I have a database with 3 tables for classes that all implement my IMeetingEntityEntityBaseClass.cs
public class BookingAppDbContext : DbContext
{
public BookingAppDbContext(DbContextOptions<BookingAppDbContext> options) : base(options)
{
}
public DbSet<MeetingSpace> MeetingSpaces { get; set; }
public DbSet<Employee> Employees { get; set; }
public DbSet<Booking> Bookings { get; set; }
}
I started off with only the MeetingSpaces table and was trying to make my database operation methods generic so they could be reused for the other two tables. However they (and my ASP.NET pages) rely on the Id to identify the meeting space. My problem is that I cant figure out how to make the GetById class generic. For the MeetingSpace table query it is as follows:
public MeetingSpace GetById(int id)
{
return db.MeetingSpaces.Find(id);
}
I attempted to use the Find overload with the type specified but the typeof(T) returns 'object', throwing an error that type 'object' cannot be converted to 'T'.
public T GetById<T>(int id) where T : IMeetingEntityBaseClass
{
return db.Find(typeof(T), id);
}
You need to use the DbContext.Set method. Also, you need to add a type constraint since the Set method relies in it (as do all EF entities.) For example:
public T GetById<T>(int id)
where T : class, IMeetingEntityBaseClass
{
return db.Set<T>().Find(id);
}
Related
public class DS2DbContext : DbContext
{
public DbSet<DocumentFileData> DocumentFileData { get; set; }
public DbSet<WatermarkFileData> WatermarkFileData { get; set; }
public DS2DbContext(DbContextOptions<DS2DbContext> options)
: base(options) { }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
}
}
public class FileDataController<T> : ODataController where T : class
{
private readonly DS2DbContext _context;
private readonly ILogger<FileDataController<T>> _logger;
public FileDataController(DS2DbContext dbContext,
ILogger<FileDataController<T>> logger)
{
_logger = logger;
_context = dbContext;
}
[EnableQuery]
public SingleResult<T> Get([FromODataUri] Guid ID)
{
var result = _context.Set<T>().Where(i => i.ID == ID);
// Error CS1061: 'T' does not contain a definition for 'ID'
// and no accessible extension method 'ID' accepting a
// first argument of type 'T' could be found
return SingleResult.Create(result);
}
}
How do I properly (and if possible, elegantly) access members of variables with type T and get their values?
Update
When I use an interface like this:
public class FileDataController<T> : ODataController where T : IFileData
{
private readonly DS2DbContext _context;
private readonly ILogger<FileDataController<T>> _logger;
public FileDataController(DS2DbContext dbContext,
ILogger<FileDataController<T>> logger)
{
_logger = logger;
_context = dbContext;
}
[EnableQuery]
public SingleResult<T> Get([FromODataUri] Guid ID)
{
var result = _context.Set<T>().Where(i => i.ID == ID);
// Error CS0452: The type 'T' must be a reference type in
// order to use it as parameter 'TEntity' in the generic
// type or method 'DbSet<TEntity>'
return SingleResult.Create(result);
}
}
I am getting an error when calling _context.Set().
What I have come up with is
var result = _context.Set<T>()
.Where(x => (Guid) x.GetType().GetProperty("ID").GetValue(x) == ID);
But this looks horribly complicated.
The story behind this
is that I have to store data of the exact same structure in two different database table depending on the data's semantics (requirements document or watermark). Since I am doing Blazor, code first, I need to have two different classes for this to make it correctly create two tables and handle them via two different controllers. However, these controllers share the exact same code and the exact same underlying data structure. Here is a simplified example just implementing an ID:
public interface IFileData
{
public Guid ID { get; set; }
}
using System.ComponentModel.DataAnnotations;
public class FileData : IFileData
{
[Key]
public Guid ID { get; set; } = Guid.Empty;
}
public class DocumentFileData : FileData { }
public class WatermarkFileData : FileData { }
Consequently, there is a controller for DocumentFileData and one for WatermarkFileData to access the proper database table for each type of data. However, all underlying operations are identical. So I hope to able to solve this via a generic class to save me the trouble of having to make identical changes to two different classes everytime something in file data handling changes.
Since you have an common interface exposing ID property provide corresponding generic constraint - (you need to specify both generic type constraints - interface and class):
public class FileDataController<T> : ODataController where T : class, IFileData
As #GuruSiton correctly commented, you should introduce an interface with ID in it:
interface IClassWithId
{
Guid ID { get; } // Notice that you only require get - the implementer can choose how the ID get init'd - private / c'tor / etc
}
public class FileDataController<T> : ODataController where T : IClassWithId
{ ... }
This assumes that you can make - or require - all classes used as T to implement IClassWithId. If you're stuck with existing classes that define their own ID and can't change them, then you must resort to reflection, as hinted to by #NaeemAhmed.
I have a base Entity type that I use as a base class for multiple entity types: Customer : Entity, Product : Entity, etc. Each of these has an existing id column named after the entity type: CustomerId, ProductId, etc. Because these all have a different name but the same function, I decided to use an unmapped generic Id column that each of these could define to reference the "real" Id column for that type:
public abstract class Entity<T> where T: Entity<T>
{
[NotMapped]
public abstract int Id { get; set; }
}
public class Customer : Entity<Customer>
{
[JsonIgnore]
public override int Id { get => CustomerId; set => CustomerId = value; }
public int CustomerId { get; set; }
}
The problem is, when I try to use it:
[HttpPut("put")]
public async Task<ActionResult<bool>>> PutEntity(List<T> entities)
{
foreach (T entity in entities)
if (!MyEntities.Any(e => e.Id == entity.Id))
return NotFound(false);
}
I get an exception The LINQ expression 'DbSet<Customer>.Any(d => d.Id == ___Id_0' could not be translated.
Is there any way I can fix this to allow LINQ to run the query with a generic reference to a column with an unknown name?
Try specifying the column name to use for the overridden Id property instead:
public class Customer : Entity<Customer>
{
[Column("CustomerId")]
public override int Id { get; set; }
Another solution is to use configuration classes. You also can create an inheritance hierarchy of these configuration classes so base entity properties are configured in a single place while you can choose to override configuration values as you see fit. Quick example:
public abstract class BaseEntity
{
public int Id { get; set; }
}
public class Customer : BaseEntity
{
// Id not needed
}
public abstract class BaseEntityConfiguration<TEntity>
: IEntityTypeConfiguration<TEntity>
where TEntity is BaseEntity
{
public void Configure(EntityTypeBuilder<TEntity> builder)
{
builder.HasKey(c => c.Id); // defined in single location
}
}
public class CustomerConfiguration : BaseEntityConfiguration<Customer>
{
public void Configure(EntityTypeBuilder<Customer> builder)
{
base.Configure(builder); // configure base class' properties
builder
.Property(c => c.Id)
.HasColumnName("CustomerId"); // override column name
}
}
That sounds like entity framework cannot translate something to sql. Shot in the dark but does this work any better?
[HttpPut("put")]
public async Task<ActionResult<bool>>> PutEntity(List<T> entities)
{
foreach (T entity in entities)
{
int entityId = entity.Id;
if (!MyEntities.Any(e => e.Id == entityId ))
return NotFound(false);
}
}
If you can't compose the Expression where you need it, compose it where you have the pieces.
Since I couldn't find a way to pass the property to the Controller, I added a new property that returns the needed Expression:
public abstract Expression<Func<T, bool>> CompareId { get; }
overrode it in the derived class:
public override Expression<Func<Customer, bool>> CompareId => (e => e.CustomerId == CustomerId);
and substituted it for the whole expression:
T entity;
if (!MyEntities.Any(entity.IsId))
return NotFound(false);
It worked!
Maybe not as clean a solution as I would have liked to come up with, but it allows me to get the expression made, and is still a lot less work than creating identical Controllers for every single entity type.
Probably the biggest caveat here is that I was only able to do this because I was basing the Any on a provided entity. If I had just been provided the Id, I wouldn't have had an existing entity to use to call the property. I guess if I run into a case like that, I'll have to figure that out from there.
I am wrapping up a generically-crafted SQL table editor for a configuration application, using Entity Framework, a Repository pattern, so on and so forth. Probably a bit overkill on the layers. In any case, I have a generic MVC controller (we'll call it MyController<T>) looking to send its calls to a generic service layer (MyServices<T>), where everything "of T" is some data model class representing a SQL table, directly. Each and every data model class has an ident field called "Id", that is implemented from a base model (that itself, is implementing an interface).
Everything is very simple and smooth, except when I need to call the FindBy option on a data set, where i have to provide a delegate to search on something of a generic type. I understand that the type constraints need to have an interface attached to it so that I can access that "Id" field in any type T, but doing so causes conflict with any controllers that explicitly implement the generic controller. So:
Generic Service layer:
public abstract class MyServices<T> : IMyService<T> where T : class, new()
{
IMyRepository<T> _MyRepository;
public MyServices(IMyRepository<T> MyRepository)
{
_MyRepository = MyRepository;
}
public IQueryable<T> FindBy(System.Linq.Expressions.Expression<Func<T, bool>> predicate)
{
return _MyRepository.FindBy(predicate);
}
}
Generic Repository:
public partial class MyRepository<T> : IMyRepository<T> where T : class, new()
{
MyTablesEntities _entities = new MyTablesEntities();
public IQueryable<T> FindBy(System.Linq.Expressions.Expression<Func<T, bool>> predicate)
{
IQueryable<T> query = _entities.Set<T>().Where(predicate);
return query;
}
}
Generic Controller:
public class MyController<T> : Controller where T : class, new()
{
private string ViewTitle = typeof(T).ToString();
private readonly IMyServices<T> _MyServices = default(IMyServices<T>);
public MyController() { }
public MyController(IMyServices<T> mtt)
{
_MyServices = mtt;
}
public ActionResult Edit(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
T editItem = _MyServices.FindBy(c => **c.Id == id**).SingleOrDefault();
if (editItem == null)
{
return HttpNotFound();
}
return PartialView(editItem);
}
}
and a Controller using MyController:
public class AccountsController : MTTablesController<Accounts>
{ }
Obviously, with this setup, the FindBy delegate clause cannot differentiate the type of T until runtime, so it fails to compile this way, giving:
'T' does not contain a definition for 'Id' and no extension method 'Id' accepting a first argument of type 'T' could be found
If, however, I add the interface (IMyTableEntity) that contains a property of 'Id' to the constraint of the generic controller:
public class MyController<T> : Controller where T : class, MyTables.DataAccess.Metadata.Base.IMyTableEntity, new()
public interface IMyTableEntity
{
int Id { get; set; }
}
I now get THIS error, over on the implementing Controller:
The type 'MyTables.DataAccess.Account' cannot be used as type parameter 'T' in the generic type or method 'MyController<T>'. There is no implicit reference conversion from 'MyTables.DataAccess.Account' to 'MyTables.DataAccess.Metadata.Base.IMyTableEntity'.
I am at an impasse here as both errors lead me back into trying the other way, neither of which are working. I need the generic 'T' on the generic controller to be smart enough to carry the Id field with it, without confusing the implementing controllers above it.
trailmax may have found it...
Your class 'MyTables.DataAccess.Account' must implement IMyTableEntity
My Data modelling is set up in this way:
EF base partial classes:
public partial class Accounts {
public int Id { get; set; }
public string Code { get; set; }
public string Description { get; set; }
}
Metadata models that contain data Annotations:
public class AccountsMetadata : MyTables.DataAccess.Metadata.Base.MyTableEntity {
[Key]
[Required]
[UniqueValidator]
public string Code { get; set; }
[Display(Name="Description")]
[StringLength(100)]
public string Description { get; set; }
}
a bootstrap that ties them together
[MetadataType(typeof(AccountsMetadata))]
public partial class Accounts { }
what i needed to do was add the implementation of the interface to the bootstrap definitions for every partial, i.e.:
[MetadataType(typeof(AccountsMetadata))]
public partial class Accounts : MyTables.DataAccess.Metadata.Base.IMyTableEntity { }
and then follow that up by setting the IMyTableEntity interface as a constraint on T in the Generic Controller, as i outlined above...
I am not sure in terms of exact technical specification for this problem to me but in simple words I am trying to create a wrapper/extension method around to save my entities.
So I added new Entity Data Model (.edmx) file to my project. That generates DbSet(s) like this-
public partial class SUContextContainer : DbContext
{
public SUContextContainer()
: base("name=SUContextContainer")
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
throw new UnintentionalCodeFirstException();
}
public DbSet<Category> Categories { get; set; }
public DbSet<Gallery> Galleries { get; set; }
public DbSet<SuperUser> SuperUsers { get; set; }
public DbSet<UserType> UserTypes { get; set; }
}
Now here I am trying to wrap this into an extension method for database operations like (save, delete, update etc..)
I tried creating it as -
public static void Save(this EntityObject objEntity)
{
try // Update Record
{
((IObjectContextAdapter)Global.Context).ObjectContext.ObjectStateManager.ChangeObjectState(objEntity, EntityState.Modified);
Global.Context.SaveChanges();
}
catch (OptimisticConcurrencyException) // Insert Record
{
((IObjectContextAdapter)Global.Context).ObjectContext.ObjectStateManager.ChangeObjectState(objEntity, EntityState.Added);
Global.Context.SaveChanges();
}
}
This method is attached to EntityObject types. Where .edmx code which it generates are of type DbContext.
So Whenever I try to save some entity with this helper method it never finds out.
var galleryEntity = new Gallery {
IsActive = true,
CategoryId = model.CategoryId,
};
galleryEntity.Save(); // the save method is not found.
I tried above method to change in -
public static void Save(this DbSet objEntity)
But this also doesn't seem to take as extension method.
What am I doing wrong.
So Whenever I try to save some entity with this helper method it never
finds out.
It will not, because gallery is just a class and is not inherited from EntityObject.
I don't suggest adding inheritence or modifiying autogenerated classes.
Use power of partial classes:
You can create patial classess for your models with interface.
public partial class Gallery : IEntity
{
//This is your class different than auto generated class by Ef.
}
Also you shouldn't use try catch for decision. That's why you should seperate update and create and make decision on upper level (without try catch).
So your extension methods should be like this.
public static int Update<T>(this T entity) where T : IEntity
{
using(var dbContext=new SUContextContainer())
{
var entry = dbContext.Entry(entity);
dbContext.Set<T>().Attach(entity);
entry.State = EntityState.Modified;
return dbContext.SaveChanges();
}
}
public static int Create<T>(this T entity) where T : IEntity
{
using(var dbContext=new SUContextContainer())
{
dbContext.Set<T>().Add(entity);
return dbContext.SaveChanges();
}
}
Your extension method will only apply to types that inherit from EntityObject.
You will either need to make all of your entity classes inherit from this EntityObject class or create another extension method that applies to the correct type.
Typically when using these kind of persistence patterns you would create an entity base class
public class Entity
{
public int Id { get; set; }
}
and each entity type inherits from it
public class Gallery : Entity
{
public int Name { get; set; }
}
Then you can have common methods that you use across entity types:
public static void Save(this Entity entity);
Using the Code First approach I have created a number of different entities that inherit from an interface IConcurrent with a property IsActive for example:
public class Currency : IConcurrent
{
public string CurrencyId { get; set; }
public string Description { get; set; }
public bool IsActive { get; set; }
}
Each time I select entities I find myself always having to include a conditional clause such as this real basic example:
db.Currencies.Where(c => c.IsActive);
My question is that is it possible to some how intercept/hook into the DbContext so that my LINQ queries will always return IsActive == true for entities that inherit the IConcurrent interface, to avoid having to explicitly add .Where(c => c.IsActive) each time?
So far I've looked at the possible methods to override in DbContext which none of them seem to fit the bill. Can anyone help?
You can use filtering on the Set<> method to get just active instances, something along the lines of:
public IQueryable<T> GetActive<T>() where T : class, IConcurrent
{
return Set<T>().Where(e => e.IsActive);
}
This method could be included in a class that inherits the DbContext class, or you could make it into an extension method, like:
public static DbContextExtensions
{
public static IQueryable<T> GetActive<T>(this DbContext context)
where T : class, IConcurrent
{
return context.Set<T>().Where(e => e.IsActive);
}
}
Conditional mapping is supported in Model First approach but it is not directly supported in Code first approach. You may have a workaround by creating a property in DBContext similar to the following;
public IQueryable<Currency> ActiveCurrencies
{
get
{
db.Currencies.Where(c => c.IsActive);
}
}