Rendering IEnumerable<string> from an entity framework generic repository - c#

I am not sure if this is possible. I have several particular tables in my database represented by entity framework's code first classes. These classes have different properties except in terms of the Id property which is always a string. I am wondering if there is anyway to create a generic repository that can select and render a sequence of just the Id property. For example something like:
class GetDbIds<T> where T : class
{
// PROPERTIES
DbContext DbContext {get;set;}
DbSet<T> DbSet {get;set;}
// CONSTRUCTOR
public GetDbIds(DbContext dbContext)
{
DbContext = dbContext;
DbSet = DbContext.Set<T>();
}
// METHODS
public IEnumerable<string> GenerateNewIdSequence()
{
return DbSet.Select(x => x.Id);
}
}
I know how to set up a basic generic repository, but I haven't came across any patterns that also let you dynamically query the repository as well.

You can constrain T to an interface that has the property:
interface IIdentifiable { string Id { get; } }
class GetDbIds<T> where T : IIdentifiable, class

Related

DbContext and DbSet issues in Generic repository

I'm trying to create a project will play the repository role in all my projects.
The Idea:
The Idea was inspired from Generic Repository pattern, so I'm trying to create a generic class will be the repository.
this class will receive the dbcontext at the Instantiation.
this class will implement an Interface.
The Interface :
interface IRepo<Tcontext> where Tcontext : DbContext
{
void GetAll<Table>();
}
The Repo class :
public class Repo<Tcontext>:IRepo<Tcontext> where Tcontext: DbContext
{
private Tcontext _Context { get; set; } = null;
private DbSet _Table { get; set; } = null;
public Repo()
{
_Context = new Tcontext();
}
public void GetAll<Table>()
{
_Table = new DbSet<Table>();
return _Context.Set<Table>().ToList();
}
}
So, the Idea, lets imagine we have this dbcontext class: DBEntities, and if I want to select all records from Client table, and after that in another line I Wanna select all records from Order table
I would use:
Repo<DBEntities> repo = new Repo<DBEntities>();
var clients repo.GetAll<Client>();
var orders repo.GetAll<Order>();
What is the problem:
the problem in Repo class.
the problem is four errors I don't have any Idea how can I solve them.
Errors:
so please, any help to solve this problem? and massive thanks in advance.
The first two errors The Table must be a reference type.... is logged as you have not defined the constraints on your function. Change the signature as shown below; using the below signature the method is putting constraint that generic type Table should be reference type.
public void GetAll<Table>() where Table : class
The third error as there is no public default constructor for DBContext. You should use the parametrized one. Check the DBContext definition here
_Context = new Tcontext(connectionString);
The fourth error will resolve automatically after adding the changes for first two as generic parameter Table constraint is defined. You can check the signature of Set function at here.

Using filtered rows as part of DBContext with derived class

I want to use class inheritance for filtering rows in database which is not absolutery normal and relational. There is table entities mapped to model Entity via EF Core:
[Table("entities")]
public class Entity
{
public int Id { get; set; }
public string Type { get; set; }
}
Type is some string that can be "A" or "B" e.g.
I want to specify class EntityA : Entity for entities with type A and accordingly for B:
public class EntityA : Entity
{
// some unique A properties
}
public class EntityB : Entity
{
// some unique B properties
}
Basically my DBContext looks like
public class ApplicationContext : DbContext
{
public DbSet<Entity> Entities { get; set; }
// ...
}
Can I defined EntitiesA and EntitiesB in my DBContext using filtering by Type?
I wanted to write it at least in stupid way:
public List<EntityA> EntitiesA
{
get
{
return Entity.Where(x => x.Type == "A").ToList();
}
}
But there is class casting problem (because code returns List, not List) and also it's not like ORM-style solution, EntitiesA is not DBSet, load query automatically and so on.
Okay, I found, It's called Discriminator in EF Core, those who interested can read here: https://learn.microsoft.com/en-us/ef/core/modeling/inheritance
I don't think it's a good idea to bring EntitiesA and EntitiesB to DBContext. Because basically, it contains some of the application domain knowledge (Business Layer), which should be completely decoupled with your DBContext (Data Access Layer).
I suggest to have a EntityLoader in Business Layer which is responsible for loading Entities from DB and return list of EntityA or B.
Regarding the class casting problem, you can fix the compile error with
return Entity.Where(x => x.Type == "A").Select(x => (EntityA)x).ToList();
However, you will get a runtime error since the Entity type is less specific than the EntityA type. Which mean you need to convert like this
return Entity.Where(x => x.Type == "A").Select(x => new EntityA(....)).ToList();

Using a template class with Entity Framework created class/table

I am using ASP.NET MVC 5 with Entity Framework. In several controllers, I need to modify some tables (during a save operation), based on an integer ID and string ReferenceTable. Instead of duplicating the code, as well as other code, I want to write a base class, derived off a controller, and then deriving all other controller classes from it.
public class BaseController : Controller
{
protected virtual Boolean SaveTable<TableMap>(MyEntities _context, int ReferenceID, string ReferenceTable) where TableMap : GenericMapTable, new()
{
var GenericTableMap = (from s in _context.Set<TableMap>()
where s.ID == ReferenceID && s.ReferenceTableName = ReferenceTable
select s).ToList();
.... code ....
}
}
The reason I have GenericMapTable is for it to know the "shape" of the class, as all classes that I will pass it in EF with have both this ID and ReferenceTable string.
public abstract class GenericMapTable
{
[Required]
public int ID {get; set;}
[Required]
public string ReferenceTableName {get;set;}
}
In one of the controllers, I try to use this template, and as I am sure you can guess, I am having problems.
public class MyController : BaseController
{
public ActionResult SaveForm(MyViewModel viewModel)
{
Using(MyEntities _context = new MyEntities())
{
... code ...
var result = SaveTable<ConcereteTable>(_context, ReferenceID, ReferenceTable)
.... code ....
}
}
}
My thought is, that in the template class, I need something that describes the class as having ID and ReferenceTable, and then deriving my tables from it, but I can't do that, since the classes are part of EF and are auto-generated. For instance, it isn't like I can derive ConcreteTable from GenericMapTable.
ConcreteTable is of course one of several tables that I might pass.
The error I get is:
The type 'Namespace.ConcreteTable' cannot be used as type parameter 'TableMap' in the generic type or method 'BaseController.SaveTable(MyEntities, int, string).' There is no implicit reference conversion from ....
I did some searching, but I can't quite seem to find a good answer on the best practices. This must be fairly common.

How to provide entity type/dbset type in Generic Repository

I have a problem with a realisation of issue discussed in one of my previous thread (How to choose specific DbSet from context by name).
I have a class:
public class MyObjectRepository<TEntity> : MyObjectRepository<TEntity>
where TEntity : MyObject
{
private readonly DbContext _dbContext;
public MyObjectRepository(DbContext dbContext)
{
_dbContext = dbContext;
}
public bool DoesRecordExist(string id)
{
return _dbContext.Set<TEntity>()
.Any(x => x.id == id);
}
}
I also have a service class that have a repository field & executes operations like DoesRecordExist(string id). I have few classes registered with EntityFramework (every of them refers to db table: MyEFClass and myEFANotherClass respectively) that look like that:
public partial class MyEFClass: MyObject
{
}
public partial class MyEFAnotherClass: MyObject
{
}
Classes are empty here because I use code-first & MyObject in fact has all fields of EF classes so the are empty & should be used only for navigation between db tables.
Now, how do I tell to context which DbSet is active. For example how to say to generic repository that I use MyEFClass or MyEFAnotherClass?
I belive it should be called from service.
I've been trying with context.Set<something>() and something.GetType() from assemblies but failed anyway.
At final moment I always have response that I use base class MyObject which is not a valid EF DbSet (just a base).
I would be happy to know your ideas or approaches.
Apparently you have something you called a service, which has MyObjectReposistory as a field. The object repository has a DbContext as field.
This service has the notion of active MyObject. It knows whether MyEfClass or MyOtherEfClass is the active one.
This means that someone should tell your service which class is the active repository. Once this is done, whenever you'd like to do something with the active repository, we know which repository is meant.
Usually repositories are designed to hide the real internals of the database. Therefore I'd not expose the types of your tables to the users of your service.
Consider using an enum:
enum ActiveRepository
{
MyEfClass,
MyOtherEfClass,
}
class Service
{
private MyEfClass myEfRepository = ...;
private MyEfClass myOtherRepository = ...;
public ActiveRepository ActiveRepository {get; set;}
private IDbSet<MyObject> GetActiveRepository()
{
if (this.ActiveRepository == MyEfClass)
return this.myEfRepository;
else
return this.myOtherEfRepository;
}

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