How to update a specific fields of class in Fluent NHibernate with out updating the whole object? [duplicate] - nhibernate

This question already has answers here:
NHibernate update on single property updates all properties in sql
(2 answers)
Closed 5 years ago.
I am using Fluent NHibernate to do my NHibernate mappings, but now I have come to a problem that I am not sure how to solve. A simplified version of the problem follows.
I have a user class:
public class User {
public virtual int Id { get; set; }
public virtual string FirstName { get; set; }
public virtual string LastName { get; set; }
}
This is the associated Fluent NHibernate Class Map
public class UserMap : ClassMap<User> {
public UserMap() {
Id(x => x.Id);
Map(x => x.FirstName);
Map(x => x.LastName);
}
}
I have two web forms. One form allows me to change the users first name, and the second form allows me to change the users last name. What I am trying to achieve is a simple SQL statement like this:
For the first form:
UPDATE [users] SET firstname='new first name' WHERE id=1
For the second form:
UPDATE [users] SET lastname='new last name' WHERE id=1
Currently NHibernate performs the following SQL on my database:
UPDATE [users] SET firstname=null, lastname='new last name' WHERE id=1
The problem in the real world application, is that there are too many properties to update on some big objects (as well as access restrictions), and it seems pointless to update the whole object, when all I want / am allowed to do is update a single property.
I am hoping that someone can provide some advice as to how I can realise this, or point me in the right direction to solve this.

Hibernate's doing the right thing, but your problem indicates that your schema needs some normalization.

Ok, that works, Thanks for the help and tips Queen3!
here is how I sovled it:
using (var sf = Repository.CreateSessionFactory()) {
using (var s = sf.OpenSession()) {
using (var t = session.BeginTransaction()) {
var existingUser = s.Get<User>(editedUser.Id);
existingUser.LastName = editedUser.LastName;
s.SaveOrUpdate(existingUser);
t.Commit();
}
}
}
Although this does work, it requires that I retrieve the User from the database first and work within the same session. The good thing is that the sql statement that is generated just updates the dirty LastName field. :-)
I am unable to get it to work with a detached instance of the user, this is similar to how I was doing it before, which resulted in every field of the user being updated.
using (var sf = Repository.CreateSessionFactory()) {
using (var s = sf.OpenSession()) {
using (var t = session.BeginTransaction()) {
s.SaveOrUpdate(editedUser);
t.Commit();
}
}
}

Related

Nhibernate mapping at run time

I am developing a site in which nhibernate is using. that is working fine for static mapping. but problem that i apply this application on existing database. so is there any way that mapping of classes took place at run time. i mean user provide tables and column names for mapping. Thanks
From your question I interpret you saying that the POCO classes exists, but you don't know the table or column names at build time.
So, if you already had this class:
public class MyGenericClass
{
public virtual long Id { get; set; }
public virtual string Title { get; set; }
}
You could bind it to a table and columns at runtime:
string tableName; // Set somewhere else by user input
string idColumnName; // Set somewhere else by user input
string titleColumnName; // Set somewhere else by user input
var configuration = new NHibernate.Cfg.Configuration();
configuration.Configure();
var mapper = new NHibernate.Mapping.ByCode.ModelMapper();
mapper.Class<MyGenericClass>(
classMapper =>
{
classMapper.Table(tableName);
classMapper.Id(
myGenericClass => myGenericClass.Id,
idMapper =>
{
idMapper.Column(idColumnName);
idMapper.Generator(Generators.Identity);
}
);
classMapper.Property(c => c.Title,
propertyMapper =>
{
propertyMapper.Column(titleColumnName);
}
);
}
);
ISessionFactory sessionFactory = configuration.BuildSessionFactory();
ISession session = sessionFactory.OpenSession();
////////////////////////////////////////////////////////////////////
// Now we can run an SQL query over this newly specified table
//
List<MyGenericClass> items = session.QueryOver<MyGenericClass>().List();
I don't think that could be possibly with NHibernate, but you could use a workaround.
You could use a view instead a table for the NHibernate mapping.
And in runtime, you could create that View or update it with the especified user mapping you need.
For example, you define a mapping in NHibernate to a view named ViewMapped with two columns Name and Mail.
And in the other hand, the user has a table with three columns Name, SecondName, EMail.
you can create a view on runtime with the following select:
(SELECT Name + ' ' + SecondName as Name, EMail as Mail FROM tableName) AS ViewMapped
I hope that helps you, or at least leads you to a solution.

Creating a new Content Item in the migration with Orchard CMS

Here's my migration code:
public Migrations(IRepository<ProductPartRecord> productPartRepository, IRepository<CategoryPartRecord> categoryPartRepository)
{
_productPartRepository = productPartRepository;
_categoryPartRepository = categoryPartRepository;
}
public int Create() {
ContentDefinitionManager.AlterTypeDefinition("Category", builder => builder
.WithPart("CommonPart")
.WithPart("TitlePart")
.WithPart("AutoroutePart"));
ContentDefinitionManager.AlterTypeDefinition("Category", builder => builder
.WithPart("AutoroutePart", partBuilder => partBuilder
.WithSetting("AutorouteSettings.AllowCustomPattern", "true")
.WithSetting("AutorouteSettings.AutomaticAdjustmentOnEdit", "false")
.WithSetting("AutorouteSettings.PatternDefinitions", "[{Name:'Category Title', Pattern: 'category/{Content.Slug}', Description: 'category/category-title'}]")));
SchemaBuilder.CreateTable("CategoryPartRecord", table => table
.ContentPartRecord()
.Column<string>("Name")
.Column<string>("Description")
.Column<string>("Image")
);
ContentDefinitionManager.AlterTypeDefinition("Category", builder => builder
.WithPart("CategoryPart"));
ContentDefinitionManager.AlterTypeDefinition("Category", builder => builder
.Creatable()
.Draftable());
return 1;
}
public int UpdateFrom1() {
_categoryPartRepository.Create(new CategoryPartRecord { Name = "Category1", Description = "Description1", Image = "Image1" });
return 2;
}
UpdateFrom1 obviously attempts to insert a dummy record, but this causes nHibernate to throw this exception:
"attempted to assign id from null one-to-one property: ContentItemRecord"
The Part Record looks like:
public class CategoryPartRecord : ContentPartRecord {
public CategoryPartRecord()
{
CategoryProducts = new List<CategoryProductRecord>();
}
public virtual string Name { get; set; }
public virtual string Description { get; set; }
public virtual string Image { get; set; }
public virtual IList<CategoryProductRecord> CategoryProducts { get; set; }
}
Any clues as to where I'm going wrong here? Google produced nothing.
Okay, so you are creating a contentpartrecord, not a content item there. What you want is something more along the lines of:
var item = _orchardServices.ContentManager.New("Category").As<CategoryPart>();
item.Name = "Bobs Item"; // Something like that...
item.ContentItem.As<TitlePart>().Title = "Yay a title"; // This syntax may be wrong, I'm very tired
_orchardServices.ContentManager.Create(item);
_orchardServices.ContentManager.Publish(item.ContentItem);
I think that is how you would do it. Maybe you would want to look into creating content items using the import/export module, that is the more common and safe way to do it.
Not sure if the answer from Hazza works. Haven't tried that.
I usually just do this: (But not sure if it's an inferior approach in some way)
var item = _orchardServices.ContentManager.New("Category");
var cpart = item.As<CategoryPart>();
var tpart = item.As<TitlePart>();
cpart.Name = "SomeName";
tpart.Title = "SomeTitle";
_orchardServices.ContentManager.Create(item);
But to address the comment by Lawrence Johnson:
Category in this case is the content item. He is creating a new Category content item, and then extracting the corresponding CategoryPart from it.
If you are getting null when trying to extract the part you're probably missing something.
In order for this to work you need to implement the CategoryPart, CategoryPartRecord, CategoryPartHandler and CategoryPartDriver. (And of course make sure to attach your CategoryPart to you Category content item. Not certain if placement.info is required, but would add it for consistency anyway.)
You can't leave any of these out if you plan to use a Part attached to a content item.
I'm not sure if/how you can create a Part with no content item, but you can create a Record with no part and no content item (Just make sure you don't inherit ContentPartRecord in your record object). If you simply want to add a record with no part or content item, then the code in UpdateFrom1 used by Ben Power would work for creating a record. (But migration part would have to be changed, taking out the content item and part, and manually setting the Id to be a primary key for the record)

How do I manage profiles using SimpleMembership?

I have an ASP.NET MVC 4 site based off the internet template. I am using the SimpleMembership which i set up with that template.
I can modify the Users table which has been creted for me but I am unsure as to the "correct" way to modify the extra fields I have added. I want Fullname, Email etc and have added them to the user table but there appears no way to update through the SimpleMembership WebSecurity.* static methods.
Are you supposed to just update those properties yourself using EF outside of the SimpleMembership API?
1 - You need to enable migrations, prefereably with EntityFramework 5
2 - Move your
WebSecurity.InitializeDatabaseConnection("DefaultConnection", "UserProfile", "UserId", "EmailAddress", autoCreateTables: true);
to your Seed method in your YourMvcApp/Migrations/Configuration.cs class
protected override void Seed(UsersContext context)
{
WebSecurity.InitializeDatabaseConnection(
"DefaultConnection",
"UserProfile",
"UserId",
"UserName", autoCreateTables: true);
if (!Roles.RoleExists("Administrator"))
Roles.CreateRole("Administrator");
if (!WebSecurity.UserExists("lelong37"))
WebSecurity.CreateUserAndAccount(
"lelong37",
"password",
new {Mobile = "+19725000000", IsSmsVerified = false});
if (!Roles.GetRolesForUser("lelong37").Contains("Administrator"))
Roles.AddUsersToRoles(new[] {"lelong37"}, new[] {"Administrator"});
}
Now EF5 will be in charge of creating your UserProfile table, after doing so you will call the WebSecurity.InitializeDatabaseConnection to only register SimpleMembershipProvider with the already created UserProfile table, also tellling SimpleMembershipProvider which column is the UserId and UserName. I am also showing an example of how you can add Users, Roles and associating the two in your Seed method with custom UserProfile properties/fields e.g. a user's Mobile (number) and IsSmsVerified.
3 - Now when you run update-database from Package Manager Console, EF5 will provision your table with all your custom properties
For additional references please refer to this article with sourcecode:
http://blog.longle.net/2012/09/25/seeding-users-and-roles-with-mvc4-simplemembershipprovider-simpleroleprovider-ef5-codefirst-and-custom-user-properties/
They made it easy to modify the profile with SimpleMembership. SimpleMembership is using the code first EF model and the user profile is defined in the file AccountModels.cs that is generated as part of the Internet template for MVC 4. Just modify the class UserProfile and add the new fields in the class definition. For example, adding a field for email would look something like this:
[Table("UserProfile")]
public class UserProfile
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int UserId { get; set; }
public string UserName { get; set; }
public string Email { get; set; }
}
Here is an example on how you would access the email field:
var context = new UsersContext();
var username = User.Identity.Name;
var user = context.UserProfiles.SingleOrDefault(u => u.UserName == username);
var email = user.Email;
Here is what the database looks like after adding the email field.
There is a good blog that describes some of the changes in SimpleMembership here. You can also find more detailed information on customizing and seeding SimpleMembership here.
if you look right around line 273 of the accountcontroller you'll find this line
db.UserProfiles.Add(new UserProfile { UserName = model.UserName });
Looks like even OOTB they (MS) are doing just as you suggested and using EF to update.
I too, am looking for the "correct" way of updating and accessing these properties.
Edit:
Here's my solution (I'm happy if someone says there's an OOTB way to do this).
wrap UserProfile (the .net Entity from SimpleMembership) in a session class.
public static class sessionHelpers {
public static UserProfile userProfile
{
get
{
if (HttpContext.Current.Session["userProfile"] != null)
{
return HttpContext.Current.Session["userProfile"] as UserProfile;
}
else
{
using (UsersContext db = new UsersContext())
{
HttpContext.Current.Session["userInfo"] =
db.UserProfiles.Where(x => x.UserName ==
HttpContext.Current.User.Identity.Name).FirstOrDefault();
return db.UserProfiles.Where(x => x.UserName ==
HttpContext.Current.User.Identity.Name).FirstOrDefault();
}
}
}
set { HttpContext.Current.Session["userProfile"] = value; }
}
}
From this you can access the profile table by doing
string foo = sessionHelpers.userProfile.FIELDNAME;
where sessionHelpers is my wrapper class. The if block just ensures that if it hasn't been set in the current session that accessing it will attempt to get it.
You need to add them to your Database ( done according to description )
add them to the view ( edit , add , delete , and view ) unless modified
add them to your model in UserProfiles
Then it will work.

Does Fluent Nhibernate Lazy loads IList from Criteria

I want to make a kind of "news feed" in the application of my company.
In the scenario, User actions will generate "Activity" of different kinds, and other users will see in their "news feed".
However, an "Activity" is not related to all users, and to determine the relation, we have a complex piece of code.
Here is my Activity class
public class Activity: IActivity
{
public virtual int Id { get; set; }
public virtual ActivityType Type { get; set; }
public virtual User User { get; set; }
public virtual bool IsVisibleToUser(User userLook)
{
// Complex business calculation etc.
return true;
}
}
I want to get latest 10 news that is visible to User. But since the Activity table will be quite huge, and performance is an issue, I want to do the best practice about it.
What i am about to do, is get 25 last Activity, and check if we fill the list to show to user. For example, if only 5 Activity is visible to user, i will get another 25 Activities and so on.
IList<Activity> resultList = session.CreateCriteria(typeof(Activity))
.SetMaxResults(25)
.AddOrder(Order.Desc("Id"))
.List<Activity>();
I want to learn, if I get the whole list ordered by Id, and check one by one if it is visible to User, would NHibernate only loads the objects that i use for me or not?
IList<Activity> resultList = session.CreateCriteria(typeof(Activity))
.AddOrder(Order.Desc("Id"))
.List<Activity>();
int count = 0;
foreach( Activity act in resultList){
if (act.IsVisible(CurrentUser)){
count++;
// Do something with act
if (count == 10)
break;
}
}
EDIT:
Here is ActivityMapping for Activity model.
public class ActivityMap : ClassMap<Activity>
{
public ActivityMap()
{
Id(x => x.Id);
Map(x => x.Type).CustomType(typeof(Int32));
References(x => x.User).Nullable();
}
}
If your question is about how the generated SQL would look like, my guess would be :
SELECT
this_.Id as Id0_0_,
this_.ActivityTypeas ActivityType_0_0_,
--Other fields
FROM dbo.ActivityType this_
WHERE
--condition
ORDER BY
--condition
Since you have mentioned that the Activity count is huge, you can make use of
ICriteria's SetFirstResult and SetMaxResult.
SetFirstResult(int) indicates the index of the first item that you wish to obtain and SetMaxResult(int) indicates the number of rows you wish to get, 25 in your case.
The ToList would load all the records in memory at once.
[UPDATE] If you need the records to be returned one by one, make use of Enumerable() -
If you expect your query to return a very large number of objects, but you don't expect to use them all, you might get better performance from the Enumerable() methods, which return a System.Collections.IEnumerable. The iterator will load objects on demand, using the identifiers returned by an initial SQL query (n+1 selects total).
Source - Link
No, the List() method pulls everything into memory at once.

How to efficiently filter a collection of child objects by a property with NHibernate

I've been trying unsuccessfully to filter a collection of child objects for a few hours how and have finally thrown my hands up! I'm new to NHibernate and was hoping for a couple of pointers on this. I've tried various ICriteria etc. with no luck. I'm just not getting it.
I have a parent object 'Post' with a collection of child objects 'Comment'. The collection is mapped as a set with inverse on the Comment side.
What I am trying to do is to return only comments with a status enum value of 'Comment.Approved'
The relevant portions of the entity classes are as follows:
public class Post
{
public virtual Guid Id { get; protected set; }
private ICollection<Comment> _comments;
public virtual ICollection<Comment> Comments
{
get { return _comments; }
protected set { _comments = value; }
}
}
public class Comment
{
public virtual Guid Id { get; protected set; }
public virtual Post Post { get; set; }
public virtual CommentStatus Status { get; set; }
}
My retrieval code looks like this at the moment:
var Id = __SomeGuidHere__;
var post = _session
.CreateCriteria<Post>()
.Add(Restrictions.Eq("Id", Id))
.UniqueResult<Post>();
var comments = _session.CreateFilter(post.Comments, "where Status = :status").SetParameter("status", CommentStatus.Approved).List<Comment>();
While this works the SQL doesn't appear to be very efficient, I expected to be able to translate the following SQL into something similar in HQL or an ICriteria of some sort:
SELECT * FROM posts p LEFT JOIN comments c ON p.PostId = c.PostId AND c.Status = 0 WHERE p.PostId = '66a2bf13-1330-4414-ac8a-9d9b00ea0705';
I've had a look at the various answers related to this type of query here and none of them seem to address this specific scenario.
There's probably something very simple I'm missing here but I'm too tired now to see it. Here's hoping someone better with NHibernate can point me in the right direction.
Thanks for your time.
Edit: Still struggling with this, some of the answers here are good in that I'm starting to think that my post entity needs to be re-thought to perform the filtration itself, or that I should implement a ViewModel to filter the comments I want. The question still remains however, even if only from an academic perspective.
I've updated the selection to HQL and tried:
var post = _session
.CreateQuery("select p from Post as p left join fetch p.Comments as c where p.Id = :id and c.Id in (select ac from p.Comments ac where ac.Status = :status)")
.SetParameter("id", Id)
.SetParameter("status", CommentStatus.Approved)
.UniqueResult<Post>();
This works as long as a post has an approved comment, otherwise I get no post due to the SQL generated using 'AND' in the where clause.
Anyone? I'm stumped now!
Update: Thanks to all who have replied, it has been useful and has forced me to re-evaluate portions of my model. Since the most frequent use of comments as children of a post is in the viewing of a post, only the approved comments should be viewable in this scenario. In most other scenarios that I can think of comments would be accessed directly and filtered by status which is of course straight forward.
I have updated my mappings to filter all post > comment loading to only load approved posts as follows (in FluentNHibernate):
HasMany(x => x.Comments).Where(x => x.Status == CommentStatus.Approved)
.AsSet()
.Inverse()
.KeyColumn("PostId")
.ForeignKeyConstraintName("PostComments")
.OrderBy("CreatedOn")
.Cascade.AllDeleteOrphan();
I wish I could mark all as the answer since all contributed to me working this thing out, but Peter was the first to point out that I may be thinking about the model incorrectly.
Thanks to all.
According to me the SQL is perfect for what you describe.
The database has its own optimizer and will know what to do to get your data efficiently delivered to your doorstep.
I think this would work fine:
var comments =
_session.CreateCriteria<Comment>
.CreateAlias("Post", "post")
.Add (Restriction.Eq("Status", status))
.Add (Restriction.Eq("post.Id", Id)).List();
I am not shure if you need the .CreateAlias part (if you don't need it, maybe Post.Id would work too - but I am not sure).
I would solve this using an extension method for IEnumerable<Comment>:
public static IEnumerable<Comment> FilterByStatus(this IEnumerable<Comment> comments, CommentStatus status)
{
return comments.Where(x => x.Status == status);
}
Let NHibernate return the entire collection then filter it as needed.