nhibernate doing eager fetch by default - nhibernate

i am using nhibenate by code mappings. for some reason it is doing eager fetching by default, whereas it should be lazy.
below is the mapping i have:
public EntityMap()
{
Lazy(true);
Id(x => x.Id, map =>
{
map.Generator(Generators.GuidComb);
map.UnsavedValue("00000000-0000-0000-0000-000000000000");
});
}
so i tried to specify the lazy(true) in the base class, so that all the relationships are done with lazy loading.
i am also using mapping by convention, which is configured as below:
// foreign key convention (many2one side)
mapper.BeforeMapManyToOne += (insp, prop, map) => map.Lazy(LazyRelation.Proxy);
mapper.BeforeMapManyToOne += (insp, prop, map) => map.Fetch(FetchKind.Select);
// bag conventions (one2many side)
mapper.BeforeMapBag += (insp, prop, map) => map.Lazy(CollectionLazy.Lazy);
mapper.BeforeMapBag += (insp, prop, map) => map.Fetch(CollectionFetchMode.Select);
// set conventions (one2many side)
mapper.BeforeMapSet += (insp, prop, map) => map.Lazy(CollectionLazy.Lazy);
mapper.BeforeMapSet += (insp, prop, map) => map.Fetch(CollectionFetchMode.Select);
so i have tried all the settings to make it fetch lazy, but its still fetching eager..
below is the query i am using to load the data:
var session = SessionManager.GetCurrentSession();
return session.QueryOver<Customer>().List();
the one/many to many mapping is specified as below:
Bag(x => x.Customer, colmap => { }, map => map.OneToMany(x => { }));
ManyToOne(x => x.Orders, map => { map.NotNullable(true); });
please help!!!
all the settings mentioned above were added to make it lazy load, initially none of the settings where specified....

Related

Unable to Save User Accounts After Renaming Identity Tables In Asp.Net MVC Core App

I have managed to customize Identity table names and IdentityUser as described on this forum. My problem is that I'm unable to save user accounts thereafter and I get the error below:
Cannot use table App Users for entity type App Users since it is being used for entity type ApplicationUser and there is no relationship between their primary keys.
I have included herein a demo app that can be downloaded from here
After customizing table names, I did add a migration and updated the database, all these operations where successful, I then used the dotnet ef dbcontext scaffold command to import other database tables. After this, I added another migration which was unsuccessful, ef framework throws the error below:
Cannot use table 'AppRole' for entity type 'AppRole' since it is being used for entity type 'IdentityRole' and there is no relationship between their primary keys.
I have reset the migrations, deleted the _migration table and started all over again but can't get it to work. I therefore beseech you to assist me in this regard.
This is a snapshot of DB-Context
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.HasAnnotation("ProductVersion", "2.2.3-servicing-35854");
foreach (var entityType in modelBuilder.Model.GetEntityTypes())
{
var table = entityType.Relational().TableName;
if (table.StartsWith("AspNet"))
{
entityType.Relational().TableName = table.Replace(table.Substring(0, 6), "App");
}
};
modelBuilder.Entity<AppRoleClaims>(entity =>
{
entity.HasIndex(e => e.RoleId);
entity.Property(e => e.RoleId).IsRequired();
entity.HasOne(d => d.Role)
.WithMany(p => p.AppRoleClaims)
.HasForeignKey(d => d.RoleId);
});
modelBuilder.Entity<AppRoles>(entity =>
{
entity.HasIndex(e => e.NormalizedName)
.HasName("RoleNameIndex")
.IsUnique()
.HasFilter("([NormalizedName] IS NOT NULL)");
entity.Property(e => e.Id).ValueGeneratedNever();
entity.Property(e => e.Name).HasMaxLength(256);
entity.Property(e => e.NormalizedName).HasMaxLength(256);
});
modelBuilder.Entity<AppUserClaims>(entity =>
{
entity.HasIndex(e => e.UserId);
entity.Property(e => e.UserId).IsRequired();
entity.HasOne(d => d.User)
.WithMany(p => p.AppUserClaims)
.HasForeignKey(d => d.UserId);
});
modelBuilder.Entity<AppUserLogins>(entity =>
{
entity.HasKey(e => new { e.LoginProvider, e.ProviderKey });
entity.HasIndex(e => e.UserId);
entity.Property(e => e.LoginProvider).HasMaxLength(128);
entity.Property(e => e.ProviderKey).HasMaxLength(128);
entity.Property(e => e.UserId).IsRequired();
entity.HasOne(d => d.User)
.WithMany(p => p.AppUserLogins)
.HasForeignKey(d => d.UserId);
});
modelBuilder.Entity<AppUserRoles>(entity =>
{
entity.HasKey(e => new { e.UserId, e.RoleId });
entity.HasIndex(e => e.RoleId);
entity.HasOne(d => d.Role)
.WithMany(p => p.AppUserRoles)
.HasForeignKey(d => d.RoleId);
entity.HasOne(d => d.User)
.WithMany(p => p.AppUserRoles)
.HasForeignKey(d => d.UserId);
});
modelBuilder.Entity<AppUserTokens>(entity =>
{
entity.HasKey(e => new { e.UserId, e.LoginProvider, e.Name });
entity.Property(e => e.LoginProvider).HasMaxLength(128);
entity.Property(e => e.Name).HasMaxLength(128);
entity.HasOne(d => d.User)
.WithMany(p => p.AppUserTokens)
.HasForeignKey(d => d.UserId);
});
modelBuilder.Entity<AppUsers>(entity =>
{
entity.HasIndex(e => e.NormalizedEmail)
.HasName("EmailIndex");
entity.HasIndex(e => e.NormalizedUserName)
.HasName("UserNameIndex")
.IsUnique()
.HasFilter("([NormalizedUserName] IS NOT NULL)");
entity.Property(e => e.Id).ValueGeneratedNever();
entity.Property(e => e.Email).HasMaxLength(256);
entity.Property(e => e.NormalizedEmail).HasMaxLength(256);
entity.Property(e => e.NormalizedUserName).HasMaxLength(256);
entity.Property(e => e.UserName).HasMaxLength(256);
});
}
Good day, here is a detailed guideline to anyone who wants to use database first approach with custom Identity tables using Asp.Net Core 2.2 and ef core.
Step 1 ==> Create your database first and add all the tables, relationships, and constraints
step 2 ==> assuming you have added all ef framework dependencies, head over to Visual Studio and create one class called e.g ApplicationUser that inherits from IdentityUser, also add custom fields there.
step 3 ==> configure your DB context class to use ApplicationUser and do the same in Startup.cs when registering AddDbContext service and also use OnModelCreating to customize Identity table names. Remember to invoke base.OnModelCreating(modelBuilder)
step 4 ==> add a migration and update database, this generates identity tables in our already existing DB.
step 5 ==> Head over to your Relational Database Management System (RDBMS) and verify that your customized Identity tables have been added. If so, then go to step 6, else troubleshoot the problem before continuing
step 6 ==> Issue dotnet ef dbcontext scaffold "connectionstring" Microsoft.EntityFrameworkCore.SqlServer -o Models/Entities -f -c "TempDbContext" command from powershell or an equivalent of this using package manager console
step 7 ==> Very Important: look for all Identity table related entities whose names were customized in step 3. Make sure these 7 entities all inherit from Identity specifying their Primary key type for example:
public partial class AppUsers : IdentityUser<string>{}
public partial class AppUserTokens : IdentityUserToken<string>{}
public partial class AppUserRoles : IdentityUserRole<string>{}
public partial class AppUserLogins : IdentityUserLogin<string>{}
public partial class AppUserClaims : IdentityUserClaim<string>{}
public partial class AppRoles : IdentityRole<string>{}
public partial class AppRoleClaims : IdentityRoleClaim<string>{}
After this go to the initial DBContext class, the one registered in ConfigureServices in startup.cs and change the class signature to
public class ApplicationDbContext : IdentityDbContext<AppUsers, AppRoles, string, AppUserClaims, AppUserRoles, AppUserLogins, AppRoleClaims, AppUserTokens>
Step 8: copy all the DbSet and everything method inside OnModelCreating from temporary TempDBContext auto-generated by the scaffold command in step 6 and replace what you have in your initial DBContext or you could alternatively make TempDBContext your main DBContext but remember to update ConfigureServices.
step 9: Update all references to ApplicationUser and make them point to AppUsers, copy all custom fields from ApplicationUser to AppUsers, the reason for doing this is that ApplicationUser doesn't have a reference table to map to in the database but AppUsers does.
step 10 ==> Update all your views and partial views injected with
#inject SignInManager<AppUsers> SignInManager
#inject UserManager<AppUsers> UserManager
to use AppUsers from ApplicationUser especially _LoginPartial.cshtml and you are done. you can now add user Accounts as follows:
var user = new AppUsers
{
Id = Guid.NewGuid().ToString(),
UserName = "example#gmail.com",
Email = "example#gmail.com",
PhoneNumber = "0123456789",
};
var result = await userManager.CreateAsync(user, "Ex#mple88#gmail.com");
Done!!! do some code clean-up and delete any files and code no longer needed, this is how I succeeded to make Identity work using Custom Table Names and DB first approach. Failure to follow these steps to the later will result in errors I posted and many others. Thank you for reading through.
You can change the table names using the below:
base.OnModelCreating(builder);
builder.Entity<UserProfile>().ToTable("UserProfile");
builder.Entity<UserProfile>().Property(up => up.Id).HasColumnName("UserId");
builder.Entity<UserRole>().ToTable("Role").Property(ur => ur.Id).HasColumnName("RoleId");
builder.Entity<UserRole>().Property(ur => ur.Id).HasColumnName("UserRoleId");
builder.Entity<IdentityUserClaim<int>>().ToTable("UserClaim");
builder.Entity<IdentityUserRole<int>>().ToTable("UserRole").HasKey(k => new {k.RoleId, k.UserId});
builder.Entity<IdentityUserLogin<int>>().ToTable("UserLogin");
builder.Entity<IdentityRoleClaim<int>>().ToTable("RoleClaim");
builder.Entity<IdentityUserToken<int>>().ToTable("UserToken");
Ref:
https://mitchelsellers.com/blogs/2017/09/05/taking-control-of-aspnet-identity-table-names

KnockoutMVC Foreach Lambda filter (or any filter)

I have been able to create a display that loops a list of pages. This will display all pages in the DB table like the following.
#using (var page = ko.Foreach(m => m.PageList))
{
#page.Html.TextBox(p => p.PageErrorMessage)
#page.Html.TextBox(p => p.PageSuccessMessage)
#page.Html.TextBox(p => p.Title)
#page.Html.TextBox(p => p.Content)
}
I am would like to be able to filter what displays by a lambda expression on the Foreach. Currently that returns the type IEnumerable, even with a ToList() at the end the following does not work.
//Note: I have tried .Where pl.Title == "string" with the same results
#using (var page = ko.Foreach(m => m.PageList.Where(pl => pl.Title.Contains("Page01")))
{
#page.Html.TextBox(p => p.PageErrorMessage)
#page.Html.TextBox(p => p.PageSuccessMessage)
#page.Html.TextBox(p => p.Title)
#page.Html.TextBox(p => p.Content)
}
I can get the results that I want, but it seems cumbersome to do this. If I add a visible check to each field with the same check I only see the fields I want.
//Note: p.Title.Contains("string") does not work for me in the Visible here
#using (var page = ko.Foreach(m => m.PageList))
{
#page.Html.TextBox(p => p.PageErrorMessage).Visible(p => p.Title == "Page01!")
#page.Html.TextBox(p => p.PageSuccessMessage).Visible(p => p.Title == "Page01!")
#page.Html.TextBox(p => p.Title).Visible(p => p.Title == "Page01!")
#page.Html.TextBox(p => p.Content).Visible(p => p.Title == "Page01!")
}
Is there a better way to work with foreach to filter down the list or is this currently designed to always return the full set?
You cannot use C# code in razor to modify your viewmodel. To bind against something it needs a representation in the viewmodel. If you want to filter the full list, a computed property on the viewmodel should do the trick.
[Computed]
public List<Page> FilteredList
{
get { return PageList.Where(pl => pl.Title.Contains("Page01")); }
}

NHibernate Issuing Additional UPDATE on Unidirectional One-to-Many

I'm using the new Map by Code pieces in NHibernate. My understanding was that an update was made in NHibernate 3 so that unidirectional one-to-many relationships would no longer insert null on the foreign key then update it to the correct value, as long as you set inverse=false on the collection and made the foreign key not nullable.
What I'm seeing is that NHibernate now INSERTs the correct foreign key, but it still issues an additional UPDATE that sets the foreign key to the value that was used in the insert?!?
Have I done something incorrectly in my mapping? (A user can have many passwords. The password object does not reference back to the user in my domain.)
mapper.Class<Password>(map =>
{
map.Table("Passwords");
map.Id(x => x.Id, x => { x.Generator(Generators.Native); x.Column("PasswordId"); });
map.Property(x => x.PasswordFormat, x => { x.NotNullable(true); });
map.Property(x => x.Salt, x => { x.Length(100); });
map.Property(x => x.PasswordValue, x => { x.NotNullable(true); x.Length(500); });
map.Property(x => x.CreateDate, x => { x.NotNullable(true); });
});
mapper.Class<User>(map =>
{
map.Table("Users");
map.Id(x => x.Id, x => { x.Generator(Generators.Native); x.Column("UserId"); });
map.Property(x => x.UserName, x => { x.NotNullable(true); x.Length(100); x.UniqueKey("UX_Users_Username"); });
map.Property(x => x.Email, x => { x.Length(100); x.Index("IX_Users_Email"); });
map.Property(x => x.IsAnonymous, x => { x.NotNullable(true); });
map.Property(x => x.IsApproved, x => { x.NotNullable(true); });
map.Property(x => x.LastActivityDate, x => { x.NotNullable(true); });
map.Property(x => x.CreateDate, x => { x.NotNullable(true); });
map.Set(x => x.Passwords, x => { x.Access(Accessor.Field); x.Inverse(false); x.Key(k => { k.Column("UserId"); k.NotNullable(true); k.ForeignKey("FK_Passwords_UserId"); }); x.Cascade(Cascade.All); x.Lazy(CollectionLazy.Lazy); }, x => x.OneToMany());
});
Note: This is using built-in NHibernate.Mapping.ByCode, not Fluent NHibernate.
Turns out I can accomplish this by setting k.Update(false) on the foreign key portion of the Passwords collection mapping.
The answer from hazzik on the following question queued me in.
https://stackoverflow.com/a/11576097/139694
It should be Inverse() not Inverse(false).
Inverse() means that other entity owns the relationship and it is responsible for providing data for NHibernate about the relationship while inserting/updating information i.e., if "user" is set to on inverse, "password" needs to take care of providing relationship info. to NHibernate.
For this you need to set up "User" reference property on "Password" entity. And while creating/updating the password, assign user property explicitly.
//create new password
Password objPassword = new Password();
objPassword.otherproperties =///assign
objPassword.User = <<---assign the user property
Currently, you have Inverse(false) which is default setting for NHibernate. In this case, insert statements will be executed with passwordid as null and then references are updated resulting in two operations.

Mapping IDictionary<Entity, Component> with MappingByCode

How can I map IDictionary<Entity, Component>? I've done this way:
Map<GeneralResourceType, Quantity>(x => x.BookedResources,
c =>
{
c.Key(ck => ck.Column("ProposedAction"));
c.Table("BookedResources");
},
k => k.ManyToMany(key => key.Column("ResourceTypeId")),
r => r.Component(qc => QuantityMapping.Mapping()));
(where GeneralResourceType is a mapped Entity and Quantity is a ValueObject). But during the call of BuildSession() exception is thrown:
NHibernate.MappingException : An association from the table BookedResources refers to an unmapped class: {MyNamespace}.Quantity.
Seams like it tries to find ClassMapping for Quantity, while value part mapped as Component.
First variant:
Map component in separate class inherited from ComponentMapping generic class.
Map dictionary property as follows:
Map(x => x.BookedResources, c =>
{
//any options access, cascade etc
});
Second variant (inline):
Map(x => x.BookedResources, x =>
{
//any options access, cascade etc
},
x => x.Element(),
x => x.Component(c =>
{
c.Class<Quantity>();
c.Property(p => p.Amount);
c.Property(p => p.Unit);
// any other properties
}
));

NHibernate and JoinAlias throw exception

I have query in HQL which works good:
var x =_session.CreateQuery("SELECT r FROM NHFolder f JOIN f.DocumentComputedRights r WHERE f.Id = " + rightsHolder.Id + " AND r.OrganisationalUnit.Id=" + person.Id);
var right = x.UniqueResult<NHDocumentComputedRight>();
Basically I receive NHDocumentComputedRight instance.
I've tried to implement the same query in QueryOver. I did this:
var right = _session.QueryOver<NHFolder>().JoinAlias(b => b.DocumentComputedRights, () => cp).Where(h => h.Id == rightsHolder.Id && cp.OrganisationalUnit.Id == person.Id)
.Select(u => cp).List<NHDocumentComputedRight>();
But I get null reference exception.
How can I implement this query in QueryOver?
Update (added mappings) - NHibernate 3.2:
public class FolderMapping: ClassMapping<NHFolder>
{
public FolderMapping()
{
Table("Folders");
Id(x => x.Id, map =>
{
map.Generator(IdGeneratorSelector.CreateGenerator());
});
//more not important properties...
Set(x => x.DocumentComputedRights, v =>
{
v.Table("DocumentComputedRightsFolder");
v.Cascade(Cascade.All | Cascade.DeleteOrphans);
v.Fetch(CollectionFetchMode.Subselect);
v.Lazy(CollectionLazy.Lazy);
}, h => h.ManyToMany());
Version(x => x.Version, map => map.Generated(VersionGeneration.Never));
}
}
public class DocumentComputedRightMapping : ClassMapping<NHDocumentComputedRight>
{
public DocumentComputedRightMapping()
{
Table("DocumentComputedRights");
Id(x => x.Id, map =>
{
map.Generator(IdGeneratorSelector.CreateGenerator());
});
//more not important properties...
ManyToOne(x => x.OrganisationalUnit, map =>
{
map.Column("OrganisationalUnit");
map.NotNullable(false);
map.Cascade(Cascade.None);
});
}
}
public class OrganisationUnitMapping : ClassMapping<NHOrganisationalUnit>
{
public OrganisationUnitMapping()
{
Table("OrganisationalUnits");
Id(x => x.Id, map =>
{
map.Generator(IdGeneratorSelector.CreateGenerator());
});
//more not important properties...
}
}
Thanks
AFAIK criteria/queryOver can only return the entity it was created for (NHFolder in your example) or columns which are set to entity with aliastobean. you could do a correlated subquery instead.
var subquery = QueryOver.Of<NHFolder>()
.JoinAlias(b => b.DocumentComputedRights, () => cp)
.Where(h => h.Id == rightsHolder.Id && cp.OrganisationalUnit.Id == person.Id)
.Select(u => cp.Id);
var right = _session.QueryOver<NHDocumentComputedRight>()
.WithSubquery.Where(r => r.Id).Eq(subquery)
.SingleOrDefault<NHDocumentComputedRight>();
I think you have a problem with the select statement, have you tried something like this:
var right = _session.QueryOver<NHFolder>()
.JoinAlias(b => b.DocumentComputedRights, () => cp)
.Select(x => x.DocumentComputedRights)
.Where(h => h.Id == rightsHolder.Id && cp.OrganisationalUnit.Id == person.Id)
.List<NHDocumentComputedRight>();
This is what is working for me so it should work in you case as well.
I would guess that the main reason behind the problem is the lack of proper overload on the Select method. In reality you would like to write it like this:
.JoinAlias(b => b.DocumentComputedRights, () => cp)
.Select(() => cp)
but the Expression<Func<object>> is not there. Hopefully it's going to be included in the next version.