In many projects I have to make a table containing users and implementing standard user-based functions such as authentication, saving, etc. So I decided to make a class library containing these functionalities, for example:
public class User
{
public int? Id {get;set;}
public string UserName {get;set;}
public string Password {get;set;}
}
public class MyDbContext : DbContext
{
public DbSet<User> {get;set;}
}
public class UserService
{
public MyDbContext Context {get;set;} // will be initialized in constructor
public User GetByUserName(string username)
{
return (from s in Context.Users where s.UserName.Equals(username) select s).SingleOrDefault();
}
// etc...
}
Now when I start a new Mvc project I add this library and extends the DbContext with custom models. The problem is I don't know how to extend the User table with some additional fields, for example:
public class MyUser : User
{
public bool IsApproved {get;set;}
}
public class CustomDbContext : MyDbContext
{
public DbSet<SomeOtherModel> {get;set;}
// problem: override DbSet in MyDbContext with class MyUser?
//public DbSet<MyUser> {get;set;}
}
In this case I also need to override the DbSet<User> of the MyDbContext. If I remove the DbSet<User> in the library my UserService class won't work anymore. Any ideas how to make an extensible framework?
You could use generics (I haven't tested this, just a thought):
public abstract class UserDbContext<TUser> : DbContext where TUser : User
{
public DbSet<TUser> Users {get;set;}
}
And then inherit:
public class CustomDbContext : MyDbContext<MyUser>
And the same generic in the UserService:
public abstract class UserService<TUser> where TUser : User
{
public UserDbContext<TUser> Context {get;set;} // will be initialized in constructor
public TUser GetByUserName(string username)
{
return (from s in Context.Users where s.UserName.Equals(username) select s).SingleOrDefault();
}
// etc...
}
Don't inherit from User, instead make User a partial class and extend User that way. That only works if your library is just a set of source files, and not compiled into an assembly.
EDIT:
Another option is to simply not define the dbcontext in your framework, and define it using your library classes in your application project. You could then call an initialization function in your framework to do the mappings you need and call it from OnModelCreating.
You could always use composition and have MyUser contain a User property. Then, you would have a brand-new DbContext that would have a DbSet.
This would create two tables in the database, but is probably the easiest. (disclaimer: haven't tried this and you may encounter cross-assembly issues)
Related
The scenario:
I have a couple websites that I'm rebuilding with Blazor, all do e-commerce. What I want to do is extract the accounting logic (i.e. Orders, OrderItems, Accounts, Transactions, etc) and data operations into an
"Accounting" DLL so I don't have to repeat the code.
I've got the above Entities defined in the DLL, then in the WebApp.Server's DbContext I have the appropriate DbSets.
In the "Accounting" DLL, I have an interface:
public interface IDbAccountringService
{
DbSet<Account> Accounts { get; set; }
//etc
}
which the DbContext in WebApp.Server implements:
public class Db : ApiAuthorizationDbContext<User>, IDbAccountringService
{
public Db(
DbContextOptions options,
IOptions<OperationalStoreOptions> operationalStoreOptions) : base(options, operationalStoreOptions)
{
}
public DbSet<Account> Accounts { get; set; }
}
Then in the "Accounting" DLL, I have the following generic class:
public class DbAccountingService<T> where T : DbContext, IDbAccountringService
{
DbContext dbContext { get; set; }
public DbAccountingService(DbContext T)
{
dbContext = T;
}
public Account[] GetAccounts()
{
//The compiler doesn't see Accounts
return dbContext.Accounts.ToArray();
//It also doesn't see Accounts on itself
return this.Accounts.ToArray();
// However, it does see all the DbContext methods
dbContext.SaveChanges();
}
}
which I instantiate and use in my controller:
[Route("accounting/accounts")]
[ApiController]
public class JournalController : BaseApiController
{
DbAccountingService<Db> _dbAccountingService;
public JournalController(Db db, MtGlobals mtGlobals) : base(mtGlobals)
{
_dbAccountingService = new DbAccountingService<Db>(db);
}
[HttpGet("get-accounts")]
public Account[] GetAccounts()
{
return _dbAccountingService.GetAccounts();
}
}
As the comments in DbAccountingService<T> indicate, the compiler recognizes that dbContext is in fact a DbContext, but it doesn't recognize that it also implements IDbAccountringService.
I'm a little fuzzy on generics, though I usually get them working, however, here, no luck.
Is what I'm trying to do possible? I want to extract all the data operations into the "Accounting" DLL so that I don't have to write duplicate code for each website.
Your dbContext field is of type DbContext:
DbContext dbContext { get; set; }
public DbAccountingService(DbContext T)
{
dbContext = T;
}
Be aware, that you constructor parameter is of type DbContext too with parameter name T. So this T has nothing to do with the generic type parameter, it's just a parameter name.
You want the dbContext property to be the generic type:
T dbContext { get; set; }
public DbAccountingService(T context)
{
dbContext = context;
}
The relevant par is, that your field has type T (because your where constraints this to implement interface IAccountingService.
so I have 2 different DbContext (ef 6.1.3 code first)
FirstDbContext
SecondDbContext
each context contains a SbSet Users that maps the user table in the corresponding database
NOTE : the data is different, DbFirst User is not DbSecond User!!
I have an abstract repository:
public abstract class Repository<TContext> where TContext : DbContext
{
public Repository(TContext ctx)
{
}
}
and 2 repositories :
public FirstRepo : Repository<FirstDbContext>
{
public FirstRepo(FirstDbContext ctx):base(ctx)
{
}
}
public SecondRepo : Repository<SecondDbContext>
{
public SecondRepo(SecondDbContext ctx):base(ctx)
{
}
}
I Have 2 different MSSQL databases related to the contexes:
DbFirst
DbSecond
I'm using dependency injection to add scoped repository and contexes, 2 database, 2 different connection.
I expected that my .Net Core application would use 2 Models
but once i get data from both the context i get
NotSupportedException: The type 'First.User' and the type
'Second.User' both have the same simple name of
'User' and so cannot be used in the same model.
Why the same model?
I know that in the same model I should have different names because EF does not look for namespaces, but in that case I shouldn't have this kind of issue.
EDIT #1 :
If I use one of the repository alone everything works as expected so i'm sure that there isn't any mispelled namespace
If I use the repositories all together i got this error, for example
var top10 = FirstRepo.GetTop10().ToList();
var sam = SecondRepo.GetByName<Second.User>("sam");
EDIT 2 (#Steve Green):
//Note that I'm not trying to do this :
public class MyTextContext : DbContext
{
public DbSet<Test.Security.Question> Security_Question { get; set; }
public DbSet<Test.Forms.Question> Forms_Question { get; set; }
}
// What I need is something like this :
public class SecurityContext : DbContext
{
public DbSet<Test.Security.Question> Question { get; set; }
}
public class FormsContext : DbContext
{
public DbSet<Test.Forms.Question> Question { get; set; }
}
Important note
If I manually ignore the "other" entity in both of the context everything works
I Remark that the context are not only in different namespaces, but also different assemblies...
// this is working!! .___.
// but i don't want to add a reference to a project just to exclude a class... it's unacceptable
public class FirstDbContext : DbContext
{
public DbSet<First.User> Users {get;set;}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Ignore<Second.User>();
}
}
public class SecondDbContext : DbContext
{
public DbSet<Second.User> Users {get;set;}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Ignore<First.Usere>();
}
}
Any suggestion different from renaming the table will be appreciated
Thanks
I'm trying to do the following... When a user submits a new article, i want to display the authors (users) username in my web application. That's why i need to "connect" these two and i want to have only 1 DbContext.
Now i'm doing it this way:
public class ApplicationDbContext : IdentityDbContext
{
public DbSet<ApplicationUser> ApplicationUsers { get; set; }
private DbSet<Article> Articles { get; set; } // This line is all i have added/changed. Everything else was auto-generated when i created a new ASP.net MVC project with individual authentication in visual studio.
public ApplicationDbContext()
: base("DefaultConnection")
{
}
public static ApplicationDbContext Create()
{
return new ApplicationDbContext();
}
}
But i'm not sure if this is the right way to do it. How should this be done properly?
UPDATE (explanation of my comment to DavidG's answer)
First i was retrieving a list of users like this:
class ApplicationUserService
{
public List<ApplicationUser> GetUsers()
{
using (var dbContext = new ApplicationDbContext())
{
return dbContext.ApplicationUsers.ToList();
}
}
}
instead of:
class ApplicationUserService
{
public List<IdentityUser> GetUsers()
{
using (var dbContext = new ApplicationDbContext())
{
return dbContext.Users.ToList();
}
}
}
The problem was that i couldn't get the UserName or any other property because of this mistake. I watched some tutorials online, no one ever mentioned that the DbSet is Users. Anyways... now i know why i don't need this property:
public DbSet<ApplicationUser> ApplicationUsers { get; set; }
This message from VS helped me (intended for other people which should stumble upon the same problem i had):
'ApplicationDbContext.Users' hides inherited member 'IdentityDbContext<IdentityUser, IdentityRole, string, IdentityUserLogin, IdentityUserRole, IdentityUserClaim>.Users'.
This is fine if you want to use one context, and the Identity tables are in the same database as your "article" table. I normally like to override OnModelCreating so I can add mapping/configurations for tables I create.
Depending on the size of the application, I leave the Identity context alone, and I create one for my application (I may include the Users table to make it easier to retrieve users). A matter of choice.
Your application just needs to inherit from the correct version of the IdentityDbContext class. You need to use the generic one so you can customise the user class with your own IdentityUser. So you context should be like this. Note the new inheritance and removal of the ApplicationUsers property. This is removed as the Identity class already has this done for you.
public class ApplicationDbContext : IdentityDbContext<IdentityUser>
{
private DbSet<Article> Articles { get; set; }
public ApplicationDbContext()
: base("DefaultConnection")
{
}
public static ApplicationDbContext Create()
{
return new ApplicationDbContext();
}
}
I am programming a few projects which have some core functionality which are similar and then their own functionality outside of that.
I was considering making a class library, using Entity Framework with Code First in order to provide some shared functionality and the database tables that go with it.
For example, I may want to use the class library to send an email and then use entity framework to log in a database table that an email is sent.
This class library would be added into another project, which also uses entity framework - in the same database. So now I would like the database to "build itself", creating the email logging table and some other functionality, e.g. products of some sort.
I have not used Entity Framework before, will having two dlls end up causing any kind of confusion because they're both pointing to the same database but expect different tables? e.g. would they be inclined to delete tables because they don't appear in the code?
Will it also cause problems if I end up over-lapping, e.g. if I want to do a join on all products (Project Entity Framework) which have had an email (Class Library Entity Framework) sent out, would I be able to do a join via linq?
You'll want to keep everything in one DbContext. You can do this by using interfaces to group the entities in each dll, then declare a concrete DbContext class that combines them all in your top-level code.
Project1:
public interface IMyProj1DbContext : IDbContext
{
DbSet<Person> People { get; set; }
DbSet<Place> Places { get; set; }
}
Project2:
public interface IMyProj2DbContext : IDbContext
{
DbSet<Customer> Customers { get; set; }
DbSet<Order> Orders { get; set; }
}
And you'll need a third project that defines the common members:
public interface IDbContext
{
int SaveChanges();
}
Now in the code where all these come together, you can declare a single DbContext class that inmplements all of the interfaces:
public class MyDbContext : DbContext, IMyProj1DbContext, IMyProj2DbContext
{
public DbSet<Person> People { get; set; }
public DbSet<Place> Places { get; set; }
public DbSet<Customer> Customers { get; set; }
public DbSet<Order> Orders { get; set; }
}
Now, you will want to write the code that uses the two different contexts and that code will live inside the individual dlls for each context. But how can you do that?
public class PersonFinder
{
public Person FindPersonByLocation(Place placeToSearch)
{
using (var db = new ???)
{
return db.People.SingleOrDefault(p => p.Location_Id == placeToSearch.Id);
}
}
}
You can't reference the concrete DbContext here because that will cause a circular dependency. The key is to inject the DbContext object at run-time:
public class PersonFinder : Disposable
{
IMyProj1DbContext _db;
public PersonFinder(IMyProj1DbContext db)
{
_db = db;
}
public Person FindPersonByLocation(Place placeToSearch)
{
return _db.People.SingleOrDefault(p => p.Location_Id == placeToSearch.Id);
}
public void Dispose()
{
// ... Proper dispose pattern implementation excluded for brevity
if (_db != null && _db is Disposable)
((Disposable)_db).Dispose();
}
}
*This is not the best way to inject a disposable object, by the way. But it is relatively safe to do it this way and it demonstrates the principle without the extra clutter.
Now you only have one DbContext and EF will generate and maintain one single database, even though you have nice logical domain silos that can operate independently.
When you want to perform a join between the silo entities you code can use the MyDbContext class directly.
If public UsersContext() is the constructor of the DbContext, why is it inheriting from the OfficeData database? How does the inheritance work? Since a database cannot be a base class.
It's not even a class.
I can understand that the UsersContext is inheriting from the built-in DbContext base class.
public class UsersContext : DbContext
{
public UsersContext(): base("OfficeData")
{
}
//contains this model
public DbSet<UserProfile> UserProfiles { get; set; }
}
It's definitely not "inheriting" anything from "OfficeData". And "OfficeData" isn't a database, it's a string. That's it. In this context, it's a string telling the base class constructor which connection string in the Web/App.config to use when it connects to the database. It has nothing to do with inheritance.
That is name of connection string in config file.