I am writing to a database log for my application. One of the fields in the table is userprofile_id which references a username. This is a nullable field, as some stuff gets written to the log without a user context.
In NHibernate, to collect the username, I really can't look at the reference column though, but rather the referenced object. In most cases this is fine; if it's null, there's just no username. So when trying to display the username associated with the log, I actually have to reference the Log.User.UserName.
The twist comes in in that I have a system account with a rigid set of permissions and no associated user profile to invoke automated tasks through the api interface. It writes to the log as userprofile_id system, which does not exist in my UserProfiles table.
To make this work, I had to remove the foreign key between the logs table and the UserProfiles table, but the data is logged, and NHibernate doesn't seem to care. It just reports the UserProfile object as null, which is technically correct.
I would actually like to see the text of the userprofile_id field through NHibernate though, even though it won't join to the UserProfiles table, so I can differentiate the log entries that have no user context and those that have a system function context.
Is there a way, in (N)Hibernate, to request the value of the reference field, rather than the associated object? Or am I going to have to do something custom?
Just add another property to your log entry class and map it as a property to the userprofile_id column.
That said, I think you should really handle the sytem account like all other accounts and have its permissions stored in the database.
EDIT
To illustrate the workaround:
public class Log
{
public virtual User User { get; set; }
//Add this property
public virtual string UserProfileId { get; set; }
public string UserProfileName
{
get { return User != null ? User.Name : UserProfileId; }
}
}
And map it to the column:
public class LogMap : ClassMap<Log>
{
public LogMap()
{
//Map it with this:
Map(l => l.UserProfileId).Column("userprofile_id");
//Don't change the existing mappings
References(l => l.User);//...
}
}
If a Log's userprofile_id is null, then Log.User will be null. You can check it through the referenced property like,
Log.User==null
or query like this,
session.Query<Log>().Where(l=>l.User==null).ToList()
This will fetch the entities not having Users (Logs with userprofile_id is null).
Relying on not-found=ignore is considered a feature designed to help with legacy databases - not something that should be designed into new systems. There are some negative performance aspects. It would be better to make sure you have the proper foreign key, or alternatively not map it as a reference in NHibernate at all (i.e. remove navigability from Log to User) - instead you would call you user lookup method if you need more than the username itself.
Related
I know this question has been asked many times, but all of the answers I've seen seem to relate to the entity in question coming from a different context from the current one, such as client/server scenarios like ASP.NET MVC.
In my case, that's not true, as all the action is happening in one code block. It's all in a Blazor server-side app if that makes any difference.
I'm using ASP.NET Identity, with the Identity model named User. I have a History model (irrelevant properties removed for clarity)...
public class History {
public int Id { get; set; }
public string UserID { get; set; }
[ForeignKey(nameof(UserID))]
public virtual User User { get; set; }
}
I am trying to create a new History item as follows...
string email = (await AuthenticationStateProvider.GetAuthenticationStateAsync())
.User.Identity.Name.ToLower();
User user = await Context.Users.SingleAsync(u => u.Email.ToLower() == email);
History h = new() {
// Other properties removed for clarity
UserID = user.Id,
};
Context.Histories.Add(h);
await Context.SaveChangesAsync();
...but this is throwing an exception...
Violation of PRIMARY KEY constraint 'PK_AspNetUsers'. Cannot insert duplicate key in object 'dbo.AspNetUsers'. The duplicate key value is...
As you can see, all of the code is happening in the one block, so both the User entity that is retrieved from the database, and the newly-created History enity that I'm trying to save should be in the same context. I can't see why it's trying to add a new User.
The context is injected into the Blazor component in the regular way...
[Inject]
private ApplicationDbContext Context { get; set; }
...and is set up in Startup.cs as follows...
services.AddDbContext<ApplicationDbContext>(options => {
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"));
options.EnableSensitiveDataLogging();
options.EnableDetailedErrors();
}, ServiceLifetime.Transient);
This is a fairly mature app, and doing this sort of thing seems to work fine everywhere else, it's just this new History item that's causing an exception, even though we do the same thing elsewhere.
I tried...
Setting the state of the user to unchanged
Adding AsNoTrackingWithIdentityResolution() when retrieving the user
Setting the User property rather than the UserId property
Setting the User property to null before saving
...but none of this helped.
As an experiment, I tried injecting a second context, and using that to get the user Id, then disposing it and setting it to null, just to make sure it wasn't hanging around. At this stage, the original context had never touched the users at all, and I had a plain string with the required Id. However, I still got the same exception.
Anyone any ideas? Thanks
Sigh, it turned out that I was not only barking up the wrong tree, but in the wrong forest!
The problem was nothing to do with the UserId property on the History item per se, it was one of the omitted (seemingly innocuous) other properties (passed in via a parameter) that had a navigation property that had a reference to a user, and that was causing the exception.
I changed the code to ignore the parameter and get the other property directly from the database, and it all works fine now.
Don't know if this will help anyone, but I thought I'd post it as an answer just in case.
My issue is in the next.
I have the next simple model in my code:
public class Client
{
public Guid Id { get; set; }
public string Name { get; set; }
}
I defined a mapping for it:
public class CustomMappings : Mappings
{
public CustomMappings()
{
For<Client>().TableName("clients")
.PartitionKey(x => x.Id);
}
}
I created the table via Table<TEntity>.CreateIfNotExist() method:
var table = new Table<Client>(session);
table.CreateIfNotExists();
And I can insert my data by the next way:
IMapper mapper = new Mapper(session);
var client = new Client
{
Id = Guid.NewGuid(),
Name = "John Smith"
};
await mapper.UpdateAsync(client);
After this, I've changed my model by adding a new property:
public class Client
{
public Guid Id { get; set; }
public string Name { get; set; }
public string Surname { get; set; }
}
I need to alter this table, because I want to add surname column to it.
Of course, I have the exception without it when I try to insert a value:
Cassandra.InvalidQueryException: Undefined column name surname
at Cassandra.Requests.PrepareHandler.Prepare(PrepareRequest request, IInternalSession session, Dictionary`2 triedHosts)
at Cassandra.Requests.PrepareHandler.Prepare(IInternalSession session, Serializer serializer, PrepareRequest request)
at Cassandra.Session.PrepareAsync(String query, IDictionary`2 customPayload)
at Cassandra.Mapping.Statements.StatementFactory.GetStatementAsync(ISession session, Cql cql, Nullable`1 forceNoPrepare)
at Cassandra.Mapping.Mapper.ExecuteAsync(Cql cql)
But class Cassandra.Data.Linq.Table<TEntity> does not contain neither nor .AlterOrCreate() nor .Alter() methods. Also, we don't have .GetAlter() method in Cassandra.Mapping.Statements.CqlGenerator.
Which way is more appropriate to solve this problem? I have two assumptions (besides creating a pull request with needed methods to datastax csharp driver repository on github :)).
To alter tables via cql script in .cql file which will be executed in c# code.
To create a new table after each changes of a model and migrate old data to it.
I'm a newbee in Cassandra and I have suspicions that needed method does not exist in the library for good reason. Maybe, are there any problems with consistency after altering because Cassandra is distributed database?
Changes in the Cassandra's schema should be done very accurately - you're correct about distributed nature of it, and when making changes you need to take into account. Usually it's recommended to make changes via only one node, and after execution of any DDL statement (create/drop/alter) you need to check for schema agreement (for example, via method CheckSchemaAgreementAsync of Metadata class), and don't execute next statement until schema is in agreement.
Talking about changes themselves - I'm not sure that C# driver is able to automatically generate the changes for schema, but you can execute the changes as CQL commands, as described in documentation (please read carefully about limitations!). The changes in schema could be separated into 2 groups:
That could be applied to table without need to migrate the data
That will require creation of new table with desired structure, and migration of data.
In the first group we can do following (maybe not a full list):
Add a new regular column to table
Drop a regular column from table
Rename the clustering column
Second group includes everything else:
Changing the primary key - adding or removing columns to/from it
Renaming of non-clustering columns
Changing the type of the column (it's really recommended to create completely new column with required type, copy data, and then drop original column - it's not recommended to use the same name with different type, as it could make your data inaccessible)
Data migration could be done by different tools, and it may depend on the specific requirements, like, type change, etc. But it's a different story.
Basically I know that find() works when it checks the entity by the stated primary id. My question is I have a Model and one of its property have [Key] data annotation but this column is not the actual primary key in the database.
Something like this
public class ModelMetadata (I'm using a metadata)
{
public int ID{get;set;} <<--actual rownum or db id
[Key]
public guid ItemId{get;set;} <<-- my desired id for find()
public string prop1{get;set;}
}
now if I use dbcontext.Model.Find(id) will it return the result based on the 'ItemId'? or the actual assign Primary key 'ID'?
Thank you.
Use a where conditional single or default like
User myUser = myDBContext.Users.SingleOrDefault(user => user.Username == username);
From Programming Entity Framework: DbContext:
One of the great things about Find is that it doesn't unnecessarily
query the database. It's also capable of finding newly added objects
that haven't yet been saved to the database. Find uses a simple set of
rules to locate the object (in order of precedence):
Look in memory for an existing entity that has been loaded from the database or attached to the context.
Look at added objects that have not yet been saved to the database.
Look in the database for entities that have not yet been loaded into memory.
Say I have an User entity and it haves a Password property which is not nullable:
Map((x) => x.Password).Column("PASSWORD").Not.Nullable();
In the create action, I manually set the Password value as it is a generated hash. It never goes to the View.
In the update, I try to save it, but I don't have the Password value. I get this error for Password propery:
PropertyValueException: not-null property references a null or transient value
This is my Update method:
public bool Update(UserViewModel input)
{
if (!IsValid(input))
return false;
var user = Mapper.Map<User>(input);
this.UserRepository.Update(user); // <- this is a wrapper for NH's Session.Update()
return true;
}
How can I tell NHibernate to ignore a property in an update?
Note: This is not the same as this question.
Update:
Here is how I use it: The Password property never goes to any View. Even in the Login action I have a generic LoginViewModel, only for it's view. That property is only used in the login process and it could be updated in the Reset password feature, where a new password is generated and sent to the related user e-mail.
I see 2 possibilities to achieve that
Get the entity before Update and update explicitly
// use 'Get()' because it uses the NHibernate cache
// if you already loaded the entity, it won't query the db and read it from the cache
var user = this.UserRepository.Get(input.Id);
user.PropertyToUpdate = ...;
this.UserRepository.Update(user);
In addition to that, you can use Dynamic-Update. But this will only work with entities that are bound to the Session. NHibernate will then only update the changed properties and not all while you are updating a entity. Otherwise NHibernate can't know which properties has changed and will update all. DynamicUpdate should only work when you got the entity from NHibernate. The Entity is then bound to the Context and NHibernate can track changes.
If all your entities are auto mapped you can use a ClassConvention to set DynamicUpdate to all your entities (or just filter the ones you want):
public class ClassConvention : IClassConvention
{
public void Apply(IClassInstance instance)
{
instance.DynamicUpdate();
}
}
As another option you can use a explicit mapping override:
public class UserOverride : IAutoMappingOverride<User>
{
public void Override(AutoMapping<User> mapping)
{
mapping.DynamicUpdate();
}
}
Use different classes for different behaviours
You can declare different classes for the same Entity. One class for User creation or password resetting that contains the password property. And one class for simple updates that don't need the password property. FluentNhibernate allows you to map different classes for the same table. But you need a little more effort in mapping or rather in AutoMappingOverrides.
Yes, this has been asked before here and here. I'm curious as to whether the approach I'm considering is architecturally sound.
Let me start off by trying to describe what I'd like to be able to do with my object model:
class Person {
ISet<Roles> Roles { get; set; }
}
class RoleDefinition {
string Name { get; set; }
}
class RoleAssignment {
RoleDefinition Definition { get; set; }
Person Person { get; set; }
}
class UserRole : RoleAssignment {
public virtual string Login { get; set; }
public virtual string Password { get; set; }
}
With the intent being to be able to work with roles in the following manner:
// Find all "users" with a matching login
from user in userRolesRepository.FindAll(u => u.Login.StartsWith("abc"))
select user.Person;
To do this, I'm considering the following data model
Person table (Id, Name)
RoleDefinition table (Id, Name)
RoleAssignment table (Id, DefId, PersonId)
UserRole table (RoleAssignmentId, Login, Password)
AdminRole table (RoleAssignmentId, ...)
I'll map UserRole and AdminRole as joined-sublass to RoleAssignment in NHibernate.
So, that's a 1:1 between Person and UserRole and AdminRole, a 1:1 between UserRole and RoleAssignment, and an n:1 between RoleAssignment and RoleDefinition.
My question is this: Is this really a good model?
Are there better ways to model this without losing the ability for each role to have strongly typed, queryable properties? How well will it scale, considering I will be adding even more roles to the system as we move along?
At first glance, I think it's a bit odd for a single user to have multiple logins and passwords, one for each role, unless you will assume that a user always belongs to a single role. For example, if I had both belonged to roles named Accountant and Salesperson, such as might happen in a small business, it would seem by the definition above that I would have two RoleDefinitions and, as such, two logins and passwords.
Aside from that, in the past, I have mapped this similarly. There is a User class, which is essentially a user profile and has properties such as string UserName, string HashedPassword, TimeZoneInfo TimeZonePreference, ISet<Role> Roles, etc, as well as a LogOn(string password) method.
The LogOn() method of my User class does things like update the FailedLogonsCount property or TemporaryLockoutLiftedAtUtc property and so forth depending on whether or not hashing the passed in password succeeds against the one stored, or it returns a non-persisted object that implements IPrincipal, which is a standard .NET interface.
In this sense, I distinguish between the user's profile (the User class) and their authentication/authorization tokens (non-persisted classes that implement IPrincipal and IIdentity so that they can participate in the various [Authorize] and Thread.CurrentPrincipal schemes used throughout the .NET framework). When the User instance creates the object that implements IPrincipal, it just passes a copy of the user's roles as an array of strings so that the IPrincipal's IsInRole() method will work.
This means that my role names are essentially magic, well-known strings, in effect being a unique key in a Roles database table. But I don't think there's much of a way around that. Indeed, my Role class looks like this:
class Role {
int? Identity { get; } // database identifier that I never really look at
string RoleEnum { get; } // the "enumeration" that is
// the well-known string, used in IsInRole()
string RoleName { get; } // a human-friendly, perhaps
// localizable name of the role
}
I don't have separate subclasses for each role type. Do UserRole and AdminRole as classes really have separate behavior intrinsically? I would submit that they are merely different data points of a generic Role class, so you don't need separate subclasses for each of them.
When you add a role to your system, you are either going to have re-compile the whole shebang with updated checks for that role (this is what I do, I don't expect to add a role frequently), or, if you really wanted to get fancy, the Role class could have a set of Permission objects or some such within them, and your code could just ask the role if role.AllowsUserToDoThis() and have all the permissions checking in one place. Configuring the role's set of Permissions would, therefore, allow you to edit fine-grained access control as the system is running, but you would lose the simplicity of the .NET-provided role-based security model.
There is, as you might have guessed, a million ways to do it, but hopefully this is some helpful feedback. Good luck!