How to generate DbSet dynamically in .net core? - c#

I use generic repository and I want to use dbcontext without declaration of dbset for each single database model. I tried some solutions but every time I've got this error :
Cannot create a DbSet for 'PointsType' because this type is not
included in the model for the context.
public new DbSet<TEntity> Set<TEntity>() where TEntity : BaseEntity
{
return base.Set<TEntity>();
}
BaseEntity used for generic repository.
Is there anyway to solve this problem ?

You have to register entities in a DbContext. It can infer relations and discover other entities by itself, but it's better to be explicit about it.
You have two options:
1. Adding DbSet<T> properties to DbContext
One way to do this is to add DbSet<TEntity> properties in DbContext class:
class AppDbContext: DbContext {
public DbSet<Product> Products { get; set; }
// ...
}
This is easy, but requires modifying DbContext for every new entity.
2. Implementing IEntityTypeConfiguration<T>
Another way is to implement IEntityTypeConfiguration<TEntity> for all entities, and let DbContext discover configurations using modelBuilder.ApplyConfigurationsFromAssembly method.
Note that there are no DbSet<TEntity> properties in the DbContext class!
class AppDbContext: DbContext
{
// no DbSet properties 🎉
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.ApplyConfigurationsFromAssembly(typeof(AppDbContext).Assembly);
}
}
class Product
{
public int Id { get; set; }
public string Title { get; set; }
}
class ProductEntityConfiguration: IEntityTypeConfiguration<Product>
{
public void Configure(EntityTypeBuilder<Product> builder)
{
builder.HasIndex(e => e.Title).IsUnique();
}
}
Now you can access a DbSet for an entity using:
var productSet = dbContext.Set<Product>();
References
https://learn.microsoft.com/en-us/ef/core/modeling/#grouping-configuration

Related

EF Core 6 EntityTypeBuilder.HasData() doesn't create seed data in migration [duplicate]

I have a base model:
public abstract class Status
{
public string updateUserName { get; set; }
}
Then a model which extends the base model defined above:
public class Item : Status
{
public int Id { get; set; }
public string Description { get; set; }
}
Then I have defined configuration classes for each:
public class ItemConfiguration : IEntityTypeConfiguration<Item>
{
public void Configure(EntityTypeBuilder<Item> builder)
{
builder.ToTable("Item", "dbo").HasKey(c => c.Id);
builder.Property(c => c.Description).IsRequired().HasMaxLength(100);
}
}
public class StatusConfiguration : IEntityTypeConfiguration<Status>
{
public void Configure(EntityTypeBuilder<Status> builder)
{
builder.Property(c => c.updateUserName).IsRequired().HasMaxLength(50);
}
Now, I have the following Context class:
public class TestDbContext : DbContext
{
public TestDbContext(DbContextOptions<TestDbContext> options) : base(options)
{
}
public DbSet<Item> Item { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.ApplyConfiguration(new ItemConfiguration());
}
}
I'm trying to figure out how to apply the Status model configurations defined in the StatusConfiguration class to all the models that extend to it (only one in this example: Item). I would like to avoid defining the same Status model configuration every time it gets used. The Status model will essentially be meta data associated with each Item record (i.e. one Item table in database containing all properties defined in both models; nothing more, and nothing less).
For example, my current implementation is the following ItemConfiguration class without using the StatusConfiguration class:
public class ItemConfiguration : IEntityTypeConfiguration<Item>
{
public void Configure(EntityTypeBuilder<Item> builder)
{
builder.ToTable("Item", "dbo").HasKey(c => c.Id);
builder.Property(c => c.Description).IsRequired().HasMaxLength(100);
builder.Property(c => c.updateUserName).IsRequired().HasMaxLength(50);
}
}
That current implementation works correctly and migrates to the database as intended. I'm simply looking for a more manageable way going forward.
My assumption is that I could extend the ItemConfiguration class to include the StatusConfiguration class but cannot find an example of that method online. I'm hoping someone with a little more experience could kindly point me in the right direction?
Let me know if additional information would be helpful.
If I understand correctly, the Status is just a base class and not a base entity participating in Database Inheritance.
In such case it's important to never refer to Status class directly inside entity model and configuration, i.e. no DbSet<Status>, no navigation properties of type Status or ICollection<Status>, no modelBuilder.Entity<Status>() calls and no IEntityTypeConfiguration<Status>.
Instead, you always have to refer to the concrete types inheriting from the Status. In order to reuse configuration code, you should use constrained generic methods or classes and pass the concrete entity types.
Since you are using IEntityTypeConfiguration classes, probably the most natural is to make your StatusConfiguration class generic:
public class StatusConfiguration<TEntity> : IEntityTypeConfiguration<TEntity>
where TEntity : Status
{
public virtual void Configure(EntityTypeBuilder<TEntity> builder)
{
builder.Property(c => c.updateUserName).IsRequired().HasMaxLength(50);
}
}
and let derived entity configuration classes derive from it:
public class ItemConfiguration : StatusConfiguration<Item>
{
public override void Configure(EntityTypeBuilder<Item> builder)
{
base.Configure(builder); // <--
builder.ToTable("Item", "dbo").HasKey(c => c.Id);
builder.Property(c => c.Description).IsRequired().HasMaxLength(100);
}
}

Is it possible to pass a DbContext from one project into another project?

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.

When using generic in the dbset of the entity framework at Migration

I made this change, before it was like this
public DbSet<Pessoa> Pessoas { get; set; }
Now it's this way
class ContextData<T> : DbContext where T : class
{
public DbSet<T> Dbset { get; set; }
public ContextData() : base("name=Connection")
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Configurations.Add(new PessoaMapping());
}
}
So now I do not know what I do in class migrations
internal sealed class Configuration : DbMigrationsConfiguration<local.blogapi.Context.ContextData
{
public Configuration()
{
AutomaticMigrationsEnabled = true;
}
protected override void Seed(local.blogapi.Context.ContextData context)
{
}
}
I know it's going to make a mistake. I do not know how to get the entities or if there is any specific command at the time of doing the migration, or if I pass some interface, such as IEntity
you're probably wrong.
A context is a group of dbset (dbsets represent sql tables), read this.
I think that you don't need one context for every table in your db.
Use of more contexts may make sense if you want to separate dbsets that have different logic (based on the schema for example, or to separate read / write tables)

EF 6.1.3 Multiple Database (MSSQL) Generic Repository "Same Table Name"

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

What is a good pattern for a repository and EF data context that return different types?

I have a nice clean domain layer in my app that was developed in a DDD fashion. The database was not considered at all when developing the domain. Property names make sense, aren't in ALL CAPS, and are relevant to my application.
Today, I am implementing a repository to pull from an existing EF DbContext. The DbContext was developed to (basically) match a poorly-designed Oracle database.
Ideally, I would like to implement a repository like this:
public interface IRepository {
IQueryable<T> Find<T>(Expression<Func<T, bool>> query) where T : IMyDomainEntity;
}
T is my domain entity. But, inside my Find method in my repository, I have to...
Somehow convert the expression to work with the DbContext
I am not sure how to do this yet.
Query the DbContext
Once the expression is 'mapped', this is simple
Somehow map to my domain object
I'm sure I can use AutoMapper or implement my own mapper.
Return an IQueryable having not made a trip to the database yet.
Not sure this is possible after all the meddling done in #'s 1 - 3
So, how has this problem been solved in the past? Are there any reusable patterns here?
Well, you're on the right track already, just implement what your say you want :)
1.You're passing an expression into your find method so, just use that expression in your Where clause
2.You just need to get the correct DbSet from your DbContext to query against, DbContext has a method to get the DbContext of a given type, use that and you can query like
public IQueryable<T> Find<T>(Expression<Func<T, bool>> query) where T : IMyDomainEntity
{
var dbSet = context.Set<T>();
return dbSet.Where(query);
}
3.If your domain objects are not the ones mapped by EF to the database, you'll need to customize your mapping against what's in your DB in your DbContext class (no need for automapper for that), so you would have something like this in your DbContext class
public class MyContext : DbContext
{
...
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<User>()
.Map(a => a.ToTable("DB_USERS"))
.Property(a => a.Email).HasColumnName("MAIL");
base.OnModelCreating(modelBuilder);
}
}
To map from the table DB_USERS in the DB to the class User, having different names for the fields, etc. here's an article on that
http://www.codeproject.com/Articles/165720/Using-the-Code-First-Model-Configuration-Classes
You could also map the properties to the correct table columns using attributes if you don't want/can't change your DbContext class
http://msdn.microsoft.com/en-us/data/gg193958
Or you can have a different set of entities that are mapped to your DB and use automapper to translate them into your domain objects, but you lose no. 4 bellos since you'll need to materialize the query to automap it to your domain model.
4.No need to do anything special, EF takes care of the that
UPDATE: Solution without having access to the DbContext (not fully generic version but works)
The idea is to create the mapping part of the repository for each domain class, so all gets binded correctly. Continueing with the User domain model and DBUser table model:
public class User : IDomainModelEntity
{
public int Id { get; set; }
public string Name { get; set; }
public string Email { get; set; }
}
public class DBUser
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int USER_ID { get; set; }
[Required]
[MaxLength(150)]
public string USER_NAME { get; set; }
[Required]
[MaxLength(260)]
public string USER_MAIL { get; set; }
}
Then you would have an abstract Repository and an a concrete repository per domain class that implements the basic GetAll query mapped:
public abstract class Repository<T> where T : IDomainModelEntity
{
protected readonly DbContext _context;
public Repository(DbContext context)
{
_context = context;
}
public abstract IQueryable<T> GetAll();
public IQueryable<T> Find(Expression<Func<T, bool>> predicate)
{
return GetAll().Where(predicate);
}
}
public class UserRepository : Repository<User>
{
public UserRepository(DbContext context)
: base(context)
{
}
public override IQueryable<User> GetAll()
{
return _context.Set<DBUser>()
.Select(u => new User
{
Id = u.USER_ID,
Name = u.USER_NAME,
Email = u.USER_MAIL
});
}
}
now to use it you will just call the find or get all on the repository...
using (var context = new CompanyDbContext())
{
var repo = new UserRepository(context);
var list = repo.Find(a=>a.Id >= 2).ToList();
list.ForEach(a => Console.WriteLine("Id: {0}, Name {1}, email {2}", a.Id, a.Name, a.Email));
}
It is not fully generic since you will need to pass a repository for each domain class you need to use, but it may be an acceptable compromise
Hope this helps

Categories

Resources