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.
Related
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);
}
I am creating 2 poco types during runtime (using reflection). These two should reference each other. Later instances of these pocos can be stored in the database using entity framework.
Currently I am faced by two problems:
It's not possible or at least I don't know how I am able to code this two-way referencing (because while whone Poco is described, the type of the other poco doesen't exist).
2.As I've found no answer to problem 1, I've decided to use object as the type of the references. So the Models now contain the following line:
$ public object Poco1 {get; set;}
And:
public object Poco2 {get; set;}
The usage of object confronts me now with another problem. Because, during the OnModelCreating an exception is thrown, that object needs to contain an id.
As much as I understand, this means, that ef core thinks, that "object" would be the type of the model, that should be referenced.
Does anybody have an idea on how I can do what I want?
Thanks :)
Whenever I hit a problem involving generics and reflection. I find it easier to start by solving the generic part of the problem, in a generic method;
public class Parent<TChild> where TChild : class
{
public ICollection<TChild> Children { get; set; }
}
public class Child<TParent> where TParent : class
{
public TParent Parent { get; set; }
}
public void DefineRelationship<TParent, TChild>(ModelBuilder modelBuilder)
where TChild : Child<TParent>
where TParent : Parent<TChild>
{
modelBuilder.Entity<TParent>()
.HasMany(p => p.Children)
.WithOne(c => c.Parent);
}
Now you need to use reflection to invoke the method with the correct types.
Your entities should look something like this:
public class Poco1
{
public int Id {get; private set;}
...
public Poco2 Poco2 {get; private set;}
public void SetPoco2(Poco2 poco2)
{
Poco2 = poco2;
}
}
public class Poco2
{
public int Id {get; private set;}
...
public int Poco1Id {get; private set;}
public Poco1 Poco1 {get; private set;}
}
And then setting them up:
async Task SomeMethod()
{
var poco1 = new Poco1();
var poco2 = new Poco2();
poco1.SetPoco2(poco2);
//at first only poco1 has reference to poco2
//and poco2 does not have reference to poco1 yet
Debug.Assert(poco1.Poco2 == poco2);
Debug.Assert(poco2.Poco1 != poco1);
await _someRepository.AddAsync(poco1);
await _someRepository.SaveChnagesAsync();
//After saving changes EF core manages the primary keys and references
Debug.Assert(poco1.Poco2 == poco2);
Debug.Assert(poco2.Poco1 == poco1);
}
and modelBuilder:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
var poco1Builder = modelBuilder.Entity<Poco1>();
poco1Builder.HasKey(x => x.Id);
poco1Builder
.HasOne(x => x.Poco2)
.WithOne(x => x.Poco1)
//Poco2 will have Poco1Id in db that will be used for reference
//Also Poco1Id does not have to be set manually, EF core takes care of that
.HasForeignKey<Poco2>(x => x.Poco1Id);
;
var poco12Builder = modelBuilder.Entity<Poco2>();
}
Also if reference is needed before saving to db SetPoco2 can be modified as:
public void SetPoco2(Poco2 poco2)
{
Poco2 = poco2;
poco2.SetPoco1(this);
}
and set method in Poco2:
public void SetPoco1(Poco1 poco1)
{
Poco1 = poco1;
}
Too way references are dangerous for big projects for poor maintainability
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);
}
}
I have this class
class abstract EntityBase
{
public abstract int Id {get; set; }
}
Now, i change a little bit my linq2sql generated class for user table (set override for Id)
public partial class User: EntityBase, INotifyPropertyChanging, INotifyPropertyChanged
{
...
public override int Id {...}
...
}
Than, i have another template helper class with this function
public static T SingleOrDefault(System.Linq.Expressions.Expression<Func<T, bool>> func)
{
System.Data.Linq.DataContext oDataContext = null;
T retData = GetAll(out oDataContext).SingleOrDefault(func);
if (retData != null)
{
retData.CurrentDataContext = oDataContext;
}
return retData;
}
And i use it like this
Helper<EntityBase>.SingleOrDefault(t => t.Id == 3);
And i get : Class member EntityBase`2.Id is unmapped. The problem is that it is using the Id from EntityBase not from the User class so my question is how can i construct the corresponding lambda expression for this so that it can refer the Id from the user class.
Hope i was clear.
Thanks.