So, I wanted to call this something like "Auditing Linq to Entities" or similar, but realize that doesn't quite encapsulate what I wanted to do. Simply put, our data modeler is required to put 4 columns on every single table within our aplication, even our cross-reference tables (the tables that represent the middle of a many-to-many relationship)
Anyhow, I've read a ton of articles that are about change tracking, which is close to what I want to do, but not exact. What I'm looking to do is to override the TSQL generation to append the column(s) that I need to update that are not included within the model.
Edit
Thinking more about this question, I realized that my example wasn't quite complete... imagine the User <---> Roles relationship and how that works. You typically create 3 tables: [Users], [Roles], and [UserRoles] which has 2 columns for referencing many users to many roles.
Now, imagine for all three tables, your loving DBA added 4 columns: CreatedBy, CreatedOn, UpdatedBy, UpdatedOn.
In Code, you'd probably have a Collection (list, collection, stack, etc.) of roles against each user, as in this C# code:
public class User
{
public int Id { get; set;}
public string Username { get; set;}
public string Firstname { get; set; }
public string Lastname { get; set; }
public string Password { get; set; }
public List<Role> Roles { get; set; }
}
public class Role
{
public int Id {get; set; }
public string Code { get; set; }
public string Description { get; set; }
}
Has anyone successfully been able to update all the update and created columns without adding those fields to their model utilizing Entity Framework, and if so, can they provide examples of this?
Same question, but for NHibernate. If NHibernate will support this, but Entity Framework won't, I'm comfortable to persuade the powers-that-be to allow us to utilize NHibernate over Entity Framework, as I'll have a valid reason for this.
I have a very similar structure. The only ways to update the fields is to add them as properties to the objects (I encapsulate them in a AuditInfo class) or to use triggers. Users are shown the audit fields in my application so I have to have them available as properties anyway. NHibernate is so extensible that you can accomplish what you propose but it would be ugly.
In my case the triggers aren't sufficient because our application has its own user management so I have to set the xBy properties in the application. We also have the triggers in place as a backup and to record changes made outside of the application (data scrubs).
The many-to-many tables present a big problem because I would have to include them in the domain model in order to set the audit fields. I don't do that, so for those tables I only have the audit info available in the trigger (i.e. everything but the actual user name).
AuditInfo class:
[Serializable]
public sealed class AuditInfo
{
public AuditInfo()
{
SetCreated(string.Empty, DateTime.Now);
}
public string CreatedBy { get; private set; }
public DateTime CreatedDate { get; private set; }
public string RevisedBy { get; private set; }
public DateTime RevisedDate { get; private set; }
public string CreatedInfo
{
get { return "Created: " + CreatedDate.ToShortDateString() + " by " + CreatedBy; }
}
public string RevisedInfo
{
get { return "Revised: " + RevisedDate.ToShortDateString() + " by " + RevisedBy; }
}
internal void SetCreated(string createdBy, DateTime createdDate)
{
CreatedBy = createdBy;
CreatedDate = createdDate;
SetRevised(createdBy, createdDate);
}
internal void SetRevised(string revisedBy, DateTime revisedDate)
{
RevisedBy = revisedBy;
RevisedDate = revisedDate;
}
}
Interface implemented by auditable entities:
public interface IAuditable
{
AuditInfo AuditInfo { get; }
}
Related
I'm having issues when I try and add a record to my DB. The view is registration and the following error occurs on _db.SaveChanges() when trying to add to Secretaries/Student.
SqlException: Invalid column name 'UsersUserID'.
Screenshot of error(1). Screenshot of Error(2)
I manually added changes to the Migration/Migration Snapshot and removed the column UsersUserID as it duplicated FK UserID. I've tried searching for the column but there are no occurrences. Deleting migrations and creating a new DB does not solve the problem either. Does anyone have any idea on how to solve this problem?
Here is my Users Model.
public class Users
{
[Key]
public int UserID { get; set; }
[Required]
public string Name { get; set; }
[Required]
public string Surname { get; set; }
public string Password { get; set; }
public string Email { get; set; }
public string AddressLine1 { get; set; }
public string AddressLine2 { get; set; }
public int SuburbID { get; set; }
public string Title { get; set; }
public int RoleID { get; set; }
public string ErrorMessage { get; set; }
public virtual ICollection<Secretaries> Secretaries { get; set; }
public virtual ICollection<Student> Student { get; set; }
public virtual Suburb Suburb { get; set; }
public virtual Role Role { get; set; }
}
Here is my Student Model
public class Student
{
[Key]
public int StudentID { get; set; }
public int UserID { get; set; }
[Required]
public DateTime DateJoined { get; set; } = DateTime.Now;
[StringLength(1)]
[Required]
public char Is_Active { get; set; } = 'T';
public virtual Users Users { get; set; }
}
Here is my secretaries model
public class Secretaries
{
[Key]
public int SecretaryID { get; set; }
public int UserID { get; set; }
[Required]
public DateTime DateJoined { get; set; } = DateTime.Now;
[StringLength(1)]
[Required]
public char Is_Active { get; set; } = 'T';
public virtual Users Users { get; set; }
}
This is the section where I get the error
public IActionResult Registration(Users users)
{
try
{
//users.Role.RoleID;
_db.Users.Add(users);
//_db.SaveChanges();
int role = users.RoleID;
int id = users.UserID;
if (role == 1)
{
Student student= new Student();
student.UserID = id;
_db.Student.Add(student);
_db.SaveChanges();
}
else if (role == 2)
{
Secretaries secretary = new Secretaries();
secretary.UserID = id;
_db.Secretaries.Add(secretary);
_db.SaveChanges();
}
return RedirectToAction("Index", "Home");
}
catch
{
return View(users);
}
}
To start, naming convention would help around losing the Plural for the entity. "Users" implies multiple, and your naming is inconsistent given "Student". EF can manage details like having a table name "Users" with an entity named "User". Worst case you can use the [Table("Users")] attribute if there are naming issues. The entity can use a meaningful name even if it doesn't match what the table is called.
The issue you are likely running into is that while EF can auto-resolve some relationships between entities and things like FKs by convention, unless you follow the known conventions closely, EF can miss some details requiring you to provide them. Personally I opt to keep configuration pretty deliberate and consistent, not to rely on convention and the occasional surprise when it doesn't work. In your case two likely issues is marrying the User to their declare FK, and then also the bi-directional mapping.
With the entity and property named "Users", EF likely cannot match this to the UserId by convention, at best it would probably be looking for "UsersId", so when it didn't find that it would look for TableNamePKName so "UsersUserID" where "Users" is the table name and "UserID" is the PK in that table. To resolve that:
[ForeignKey("UserID")]
public virtual Users Users { get; set; }
This tells EF where to look. If this is a later version of C# then:
[ForeignKey(nameof(UserID))]
... to avoid magic strings if available.
The next detail that may trip up the mapping will be the bi-directional references where a Secretary has a User and User has a collection of secretaries. Bi-directional references should be avoided unless they serve a very clear and useful purpose. In most cases they are not needed, but where you do you them it helps to declare the inverse side of the relationship:
[ForeignKey("UserID")]
[InverseProperty("Secretaries")]
public virtual Users Users { get; set; }
This tells EF to expect a navigation property called "Secretaries" on the Users entity which will link via this User reference.
Bi-directional references can cause issues and require more attention when updating entities, especially if you have code that does something like serialize an entity. They often aren't needed as you can typically query data from one side of the relationship.
For instance to get all Secretaries associated to a given user you might think you need something like:
var user = context.Users.Include(u => u.Secretaries).Single(u => u.UserID == userId);
var secretaries = user.Secretaries;
... when instead you can use:
var secretaries = context.Secretaries.Where(s => s.User.UserID == userId).ToList(); // or (s => s.UserID == userId) where the UserID FK is exposed.
Depending on the purpose of the association you schema may better be served by inheritance where instead of a "has a" relationship, the Secretary/Student to User becomes an "is a" relationship. Does a User "have" Secretaries/Students, or is it more that a User "Is a" Secretary vs. a Student? Alternatively, is "Student" vs. "Secretary" more of a "Role" that can be held by a User? (Which suits a case where one user might be both a Student and a Secretary)
With the schema you currently have defined, it would allow one user to be potentially associated with many Students, as well as many Secretaries which may, or may not be the desired and intended use. (Also known as Many-to-One relationships)
Edit: To implement a schema where a User can be one of these Types or Roles there are a couple of options.
Inheritance: Where a User "is a" Teacher or "is a" Student etc. EF can support inheritence where you would have something like:
public abstract class User
{
public int UserId { get; set; }
// other common fields...
}
public class Student : User
{
// Student-specific fields...
}
public class Secretary : User
{
// Secretary-specific fields...
}
From here you can configure EF to either use a single table (Users) with a discriminator column (Think "UserType" or "Role" to indicate Student vs. Secretary, etc.) or to use a User Table plus Student and Secretary tables. You can learn more about inheritance options by looking up "Table-per-Hierarchy" or "Table-per-Concrete Type" in relation to EF. If the different types of users have different fields then Table-per-concrete type is generally a better option. Table-Per-Hierarchy can work but you end up with several null-able-columns as the table needs to cater to the lowest denominator.
If the different types of users consist of the exact same data then you could use something called a Many-to-one relationship between a User table, and a UserType or a UserRole table which would identify whether the user was a Student, Teacher, etc. This can be either Many-to-One, (many users can be the same role, but each user can have only 1 role) or it can be adapted to many-to-many if users could later hold 2 or more roles.
Many-to-one:
public class User
{
public int UserId { get; set; }
public int RoleId { get; set; }
[ForeignKey(nameof(RoleId))]
public virtual Role Role { get; set; }
}
public class Role
{
public int RoleId { get; set; }
public string Name { get; set; }
}
Many-to-Many
public class User
{
public int UserId { get; set; }
public int RoleId { get; set; }
public virtual ICollection<Role> Roles { get; set; } = new List<Role>();
}
public class Role
{
public int RoleId { get; set; }
public string Name { get; set; }
}
This would need to be configured with a .HasMany(x => x.Roles).WithMany() relationship nominating a joining table (I.e. UserRoles) consisting of a UserId and RoleId. Optionally in a Many-to-Many relationship you can define the joining table as an entity if there are details about the relationship you want to access. (beyond just the keys relating the entities)
The other advantage of using a Many-to-One or Many-to-Many vs. inheritance is that it's easy to express optional roles, where a user might not be any of the roles. You can still adapt something like a Many-to-One implementation to have Student-specific data vs. Teacher-specific data by introducing a One-to-zero-or-one relationship between User and a table like StudentUserDetails on the UserId shared by both tables.
Ultimately there are a number of ways you can manage relational relationships for data in a database where EF can be configured to understand and map those relationships either within, or between entities.
I'm building a self-service reporting tool in Blazor, that allows a user to select a particular dataset (all of which are stored using Entity Framework), then filter by various properties/columns in the database.
Is there a way to abstract from coding in "types" on the web server (like using IQueryable or something of the sort) to build a LINQ command dynamically based on user selections?
For example, say we have the following two classes:
public class Car
{
public int Year { get; set; }
public string Color { get; set; }
public Company Make { get; set; }
public string Model { get; set; }
public int Weight { get; set; }
public int MilesPerGallon { get; set; }
...
}
and
public class Company
{
public string Name { get; set; }
public Country CountryOfOrigin { get; set; }
public DateTime Founded { get; set; }
...
}
A user in a user interface would be able to selct "Car" as the database, then select "Where" clauses by restricting properties to various values, and then choose to "slice" on a different property.
An example might be:
Population: Cars, Where:
Year is between 1970 and 1979.
The Car's Make (Company) CountryOfOrigin is 'United States'
Slice By: Make
Sort By: Count, Descending
Color By: Miles Per Gallon
How would I write this in such a way that I can abstract the code that builds the SQL query from individual tables / properties etc..
Additionally what would be the "best" way to create metadata on the tables themselves to exclude a table or column (Id) etc. from the front end tool, create my own attributes and dynamically exclude those from the client?
For example, I have a EF6 model like this:
class User
{
public int Id { get; set; }
public string Email { get; set; }
public string Name { get; set; }
public virtual ICollection<ProfileProperty> Properties { get; set; }
public virtual ICollection<Book> Books { get; set; }
}
class Book
{
public int Id { get; set }
public int Name { get; set }
public DateTime CreationDate { get; set }
public long Size { get; set }
public string ContentPath { get; set }
}
And now I want to create a WebAPI that allows to:
Create a new user
Update user's name
Modify the list of user's books
However, here are a few tricks to it which don't let me use tutorials right off:
Some fields are either irrelevant or confidential and must not be exposed via WebAPI, for example: User.Id, User.Properties, and nested User.Books[x].ContentPath.
Only a small subset of fields is editable (in the example, User.Name).
Only a small subset of operations (CRUD) is available, therefore it's not a REST service.
The first thing that comes to mind is create extra classes for each exposed model. However, maintaining them and writing code that converts data from database models to those WebAPI-friendly classes and back is too bothersome. Is there a more simple and automated way?
The ideal approach would be one which requires writing as little redundant code as possible. Maybe there is a set of attributes to mark fields with?
You're right in thinking you should create more classes. For each exposed action (change name, create user, etc...) you should create a ViewModel that exposes only the fields you need.
public class ChangeUserNameViewModel
{
public int UserId { get; set; }
public string NewName { get; set; }
}
It's easy to convert your view model to your domain model and back again using something like AutoMapper.
I have the fallowing 2 classes:
[Table("People")]
public class Person : IPerson
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public string FirstName { get; set; }
public Person()
{
Results = new Collection<Result>();
}
public string LastName { get; set; }
public string Name
{
get
{
return FirstName + " " + LastName;
}
set{}
}
public string Email { get; set; }
public DateTime? LastModified { get; set; }
public virtual ICollection<Result> Results { get; set; }
}
and
[Table("UserProfile")]
public class UserProfile : Person
{
public UserProfile()
{
Faculty = new Faculty();
Projects = new Collection<Project>();
}
public string UserName { get; set; }
public string CNP { get; set; }
public virtual Faculty Faculty { get; set; }
public virtual ICollection<Project> Projects { get; set; }
}
Every time i use EF CodeFirst to generate my DB, and try too run the seed method i get an error. An error occurred while updating the entries. See the inner exception for details. System.Data.SqlClient.SqlException: Cannot insert explicit value for identity column in table 'UserProfile' when IDENTITY_INSERT is set to OFF. You can find more about it here Seeding Membership with custom data Entity framework
I found out that to fix it after running update-database from the package manager console i have to open the table in server explorer and set Idendtity Specification to false for the Id in the UsersProfile table, my question is can i do something to my model so that i won't have to this every time i regenerate my Db.
I tried the answer from here Identity specification set to false but because of the inheritance (i think) i get
Conflicting configuration settings were specified for property 'Id' on type 'Prometheus.Models.Person':
DatabaseGeneratedOption = None conflicts with DatabaseGeneratedOption = Identity
Thank you.
I'm not totally sure exactly what is happening here but I do know the UserProfile table is often created with a call to WebSecurity.InitializeDatabaseConnection rather than the migrations code and that's going to put in an IDENTITY field. So there is a conflict between WebSecurity and Entity Framework here.
Then you are adding inheritance into the picture and because you have specified table names it is Table per Type - and Entity Framework wants that to use a shared primary key. So it probably does not want the UserProfile table to have an IDENTITY field.
I think you hit the nail on the head when you said
UserProfile inherits from People, that's my problem
I would change that relationship so that Person has a UserProfile instead. I think that models the real world more accurately and if you ever have any people who aren't users it will be easier to make the relationship optional. Like this:
[Table("People")]
public class Person : IPerson
{
//avoids virtual call in constructor
private ICollection<Result> _Results;
public Person()
{
_Results = new Collection<Result>();
}
//no annotations required.
//An int field named Id is a database generated key by convention
public int Id { get; set; }
//Person has a UserProfile (optional)
public int? UserProfileID { get; set; }
public UserProfile UserProfile { get; set; }
//etc
public virtual ICollection<Result> Results
{
get { return _Results; }
set { _Results = value; }
}
}
public class UserProfile : ModelBase
{
//UserProfile is always related to a Person
public int PersonID { get; set; }
public UserProfile Person { get; set; }
//etc
}
You will also find loads of stuff about preferring composition over inheritance.
Otherwise you need to dig in to the migrations to get the tables created in the way that supports TPT - but you should also be aware that switching identity on/off in migrations does not happen automatically.Perhaps you could create the UserProfile table in a migration instead of with WebSecurity.InitializeDatabaseConnection
I have a system where I need to be able to add a Comment field onto Customer and Location models but I cannot touch the schema of the existing tables. However, I can add a Comments table. I have simplified this example. We would like the ability to add this Comment to more models moving forward they all use a Guid as Id.
This existing system is a 3rd party system with its own data access layer.
We are just starting to get into NHibernate. From what I can tell it looks like a Join map.
Example:
public class Customer
{
public Guid Id { get; private set; }
public string FirstName { get; private set; }
public string LastName { get; private set; }
public string Comment { get; set; }
}
public class Location
{
public Guid Id { get; private set; }
public string Name { get; private set; }
public string Address { get; private set; }
public string Comment { get; set; }
}
Note: we are sure we want the Comment as a 1-to-1 relationship and not a 1-to-many.
How do I configure a separate table just capture Id and Comment? I'm looking for the right terminology to use. I'm looking for examples with XML (and if possible Fluent config). I would like to keep the Comments for all objects in one table. Thanks.
If you can add Comment table (and corresponding key columns in the existing tables) than fluent mapping can look like
public class CustomerMap : ClassMap<Customer>{
public CustomerMap(){
//...other columns mappings
References(c=>c.Comment).Column("CommentId");
}
}
And repeat it for other entities as well. You can set desired fetch-mode(join) and other action there as well. I have wrote References there (so many-to-one) but if you need one-to-one mapping it is not a big difference
If you can't change the database schema your options are very limited.
MAYBE, you can do it using the mapping.
Take a look here:
http://ayende.com/blog/3961/nhibernate-mapping-join
Try to use the same column name in mapping for all entities.