I'm trying to implement Soft Delete in a .NET Core 1.1.* Web App, backed by Entity Framework Core 1.1.*. I'm using Sql Server as my DB.
Migrating to .NET core 2.* is not an option at the moment.
After reading books, tuts and 3ds, I've implemented this feature using a Discriminator column. The deletion procedure is apparently working as expected. What's wrong is the data retrieval: deleted entities are still shown within my EF query results.
Current situation
Here's some C# code. I'll keep things as simple as possible
The interfaces:
// Soft deletion interface
public intercace ISoftDeletable
{}
// Another interface for some shadow properties
public interface IEntity
{}
The base class:
public abstract class Entity : IEntity, ISoftDeletable
{
public int MyBaseProp { get; set; }
}
One of my derived classes:
public class MyDerivedEntity: Entity
{
public string Name { get; set; }
public IList<MyChildEntity> Children { get; set; }
}
public class MyChildEntity: Entity
{
public string MyChildProp { get; set; }
}
The Context
public class MyContext: DbContext
{
public MyContext(DbContextOptions<MyContext> options)
: base(options)
{ }
public DbSet<MyDerivedEntity> EntitiesToUse { get; set; }
protected override void OnModelCreating(ModelBuilder builder)
{
foreach (var entity in builder.Model.GetEntityTypes())
{
if (typeof(IEntity).IsAssignableFrom(entity.ClrType))
{
builder.Entity(entity.ClrType).Property<string>("MyShadowProperty");
}
if (typeof(ISoftDeletable).IsAssignableFrom(entity.ClrType))
{
// Discriminator column
builder.Entity(entity.ClrType).HasDiscriminator("IsDeleted", typeof(bool)).HasValue(false);
// Shadow Property
builder.Entity(entity.ClrType).Property(typeof(bool), "IsDeleted").IsRequired(true).HasDefaultValue(false);
builder.Entity(entity.ClrType).Property(typeof(bool), "IsDeleted").Metadata.IsReadOnlyAfterSave = false;
}
}
// Other model configurations
base.OnModelCreating(builder);
}
// SaveChangesAsync are almost the same
public override int SaveChanges()
{
AuditEntities();
return base.SaveChanges();
}
private void AuditEntities()
{
foreach (EntityEntry<IEntity> entry in ChangeTracker.Entries<IEntity>())
{
// do something with MyShadowProperty...
}
foreach (EntityEntry<ISoftDeletable> entry in changeTracker.Entries<ISoftDeletable>().Where(w => w.State == EntityState.Deleted))
{
// Set the entity as Softly Deleted
entry.Property("IsDeleted").CurrentValue = true;
// Ensure the entity state is modified to prevend hard deletion
entry.State = EntityState.Modified;
}
}
}
The Problem
Everything works as expected, except the data retrieval.
Here's a sample call:
var results = await _context.EntitiesToUse.Include(e => e.SomeChildEntity).AsNoTracking();
I expect the results to include only available myDerivedEntities with .IsDeleted == false. The problem is that my deleted entities are not filtered out. Why?
Please, what's wrong with my code? Am I missing something?
Thank you all so much!
Entity Framework Core 2.0 supports Global Query Filter
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<ISoftDeletable>().HasQueryFilter(e => !e.IsDeleted);
base.OnModelCreating(modelBuilder);
}
You can find more info and examples here
I recommend you the built-in EF Core Global Query Filter but in some situations, Entity Framework Plus could also help.
Disclaimer: I'm the owner of this project
EF+ Query Filter allows you to filter DbSet Globally and by Instance.
// using Z.EntityFramework.Plus; // Don't forget to include this.
var ctx = new EntitiesContext();
ctx.Filter<IUser>(q => q.Where(x => !x.IsSystemUser ));
// SELECT * FROM Customers WHERE IsSystemUser = FALSE
var list = ctx.Customers.ToList();
Wiki: EF+ Query Filter
EDIT: Answer sub-question
Please, Is your library compatible with EF Core 1.1
Yes, it should be compatible with .NET Standard 1.3
Related
I need to remove all records in a given table, using a DbContext in Entity Framework. I need this to be portable, so it can't rely on SQL statements or stored procedures. I've looked through the answers to this question, but none of them meet my requirements or are ideal solutions for a variety of reasons.
I could use the RemoveRange method, i.e.
DbContext.Table.RemoveRange(all);
But this doesn't scale well, because it selects all entries before deleting, which could take a long, long time with Entity Framework. Iterating through each record and removing them individually with Remove(record) has the same problem.
Using SQL this is simple, using a TRUNCATE command. Even a simple DELETE FROM [TableName] command works, but I don't know how scalable that is.
Is there any solution that uses only Entity Framework (no SQL), and doesn't require selecting all records first before deleting them?
This is currently not something that is possible using Entity Framework. see https://github.com/dotnet/efcore/issues/795
There may be an extension out there that will allow you to do that, but I am not sure it will work will all RDBMS systems.
Let's suppose you have BrandData table with records about some brands:
public class BrandData
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
}
Also you've created a dbcontext:
public class MyDbContext : DbContext
{
public MyDbContext(DbContextOptions<MyDbContext> options) : base(options)
{
Database.Migrate();
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<BrandData>().HasData(new BrandData { Id = 999, Name = "LG", Description = "TV brand" });
}
public DbSet<BrandData> BrandData { get; set; }
}
Finally, this is the method that deletes brand by its ID:
public async Task DeleteModelAsync(int id)
{
var data = _dbContext.ModelData.FirstOrDefault(b => b.Id == id);
if (data != null)
{
_dbContext.ModelData.Remove(data);
await _dbContext.SaveChangesAsync();
}
}
Changes will be done after SaveChangesAsync() method run.
UPDATE
To delete all records:
var brands = await _dbContext.BrandData.ToListAsync();
foreach(var brand in brands)
{
_dbContext.BrandData.Remove(brand);
}
await _dbContext.SaveChangesAsync();
I am using entity framework core in a normal .net project, version 4.7. I know I can do this. The problem is that I can't seem to map an entity to a table because the "ToTable" method doesn't exist. I can't edit the poco or entity classes because they are predefined and generated. So I can't use the attribute. I looked on the internet and everyone seems to use this method to map an entity to a table.
Here is my code:
public class FactsDbContext : DbContext
{
public DbSet<TblIncident> TblIncidents { get; set; }
public DbSet<TblAction> TblActions { get; set; }
public DbSet<TblAddressTypeAlias> TblAddressTypeAliases { get; set; }
public DbSet<TblCountry> TblCountries { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
//these methods don't exist in my case
modelBuilder.Entity<TblIncident>(entity => entity.ToTable("Incident"));
modelBuilder.Entity<TblIncident>().ToTable("Incident");
}
}
I also tried to use IEntityTypeConfiguration with a EntityTypeBuilder but it still don't have access to the map to table method:
public class IncidentConfig : IEntityTypeConfiguration<TblIncident>
{
public void Configure(EntityTypeBuilder<TblIncident> builder)
{
builder.ToTable("Incident");
}
}
I looked into the Entity Framework Core repository on GitHub and searched for the method "Totable" inside the repository. It turns out it is defined as an extension method but it is in separate nuget package and library called Microsoft.EntityFrameworkCore.SqlServer
After I downloaded the package I got the Totable method that I need. Still it doesn't make sense to add that method in a separate package for sql server when you already have the "Table" attribute that you can add on entities directly in the entity framework core package.
You can use the below approach. You have to use Table data annotation.
DBContext:
public virtual DbSet<Article> Article { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Article>(b =>
{
b.Property(e => e.Property1).HasDefaultValue(true);
... //Other properties
}
Model class:
[Table("Article")]
public class Article
{
You can also use to ToTable in DBContext, but you have to make sure that you have included using Microsoft.EntityFrameworkCore;.
Line modelBuilder.Entity<TblIncident>().ToTable("Incident"); looks correct according to the documentation.
https://learn.microsoft.com/en-us/ef/core/modeling/relational/tables#fluent-api
It's very old thread but I got the same issue and I solved it by placing base.OnModelCreating(builder) as a first line of OnModelCreating method.
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
// Rest of the code
}
Imagine that I want to add an IsDeleted colum or some auditing columns to all of my entities. I could create a base class from which all of my entities will inherit and this will solve my problem, however I cannot specify the order in which the column will be created so I will end up with all the auditing fields before the fields of my entity, which I do not want. I want them to be at the end of the table.
In the standard version of entity framework we can do this by using annotations that specify the order of the columns. However, such a thing does not exist for EF core at the moment.
I could do it with the fluent api on the OnModelCreating() method, the problem is that I only know how to do it individually for each of my entities, which means I would have to write the same code for every entity I have.
Is there any way I can do it generically for all of my entities? Some sort of for loop that iterates through all the entities registered in the DbSets on my dbcontext?
Your question title is about adding the same properties to multiple entities. However, you actually know how to achieve this (use a base type) and your actual question is how to ensure that these properties come last in the generated tables' columns.
Although column order shouldn't really matter nowadays, I'll show an alternative that you may like better than a base type and also positions the common properties at the end of the table. It makes use of shadow properties:
Shadow properties are properties that are not defined in your .NET entity class but are defined for that entity type in the EF Core model.
Most of the times, auditing properties don't need much visibility in the application, so I think shadow properties is exactly what you need. Here's an example:
I have two classes:
public class Planet
{
public Planet()
{
Moons = new HashSet<Moon>();
}
public int ID { get; set; }
public string Name { get; set; }
public virtual ICollection<Moon> Moons { get; set; }
}
public class Moon
{
public int ID { get; set; }
public int PlanetID { get; set; }
public string Name { get; set; }
public Planet Planet { get; set; }
}
As you see: they don't have auditing properties, they're nicely mean and lean POCOs. (By the way, for convenience I lump IsDeleted together with "audit properties", although it isn't one and it may require another approach).
And maybe that's the main message here: the class model isn't bothered with auditing concerns (single responsibility), it's all EF's business.
The audit properties are added as shadow properties. Since we want to do that for each entity we define a base IEntityTypeConfiguration:
public abstract class BaseEntityTypeConfiguration<T> : IEntityTypeConfiguration<T>
where T : class
{
public virtual void Configure(EntityTypeBuilder<T> builder)
{
builder.Property<bool>("IsDeleted")
.IsRequired()
.HasDefaultValue(false);
builder.Property<DateTime>("InsertDateTime")
.IsRequired()
.HasDefaultValueSql("SYSDATETIME()")
.ValueGeneratedOnAdd();
builder.Property<DateTime>("UpdateDateTime")
.IsRequired()
.HasDefaultValueSql("SYSDATETIME()")
.ValueGeneratedOnAdd();
}
}
The concrete configurations are derived from this base class:
public class PlanetConfig : BaseEntityTypeConfiguration<Planet>
{
public override void Configure(EntityTypeBuilder<Planet> builder)
{
builder.Property(p => p.ID).ValueGeneratedOnAdd();
// Follows the default convention but added to make a difference :)
builder.HasMany(p => p.Moons)
.WithOne(m => m.Planet)
.IsRequired()
.HasForeignKey(m => m.PlanetID);
base.Configure(builder);
}
}
public class MoonConfig : BaseEntityTypeConfiguration<Moon>
{
public override void Configure(EntityTypeBuilder<Moon> builder)
{
builder.Property(p => p.ID).ValueGeneratedOnAdd();
base.Configure(builder);
}
}
These should be added to the context's model in OnModelCreating:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.ApplyConfiguration(new PlanetConfig());
modelBuilder.ApplyConfiguration(new MoonConfig());
}
This will generate database tables having columns InsertDateTime, IsDeleted and UpdateDateTime at the end (independent of when base.Configure(builder) is called, BTW), albeit in that order (alphabetical). I guess that's close enough.
To make the picture complete, here's how to set the values fully automatically in a SaveChanges override:
public override int SaveChanges()
{
foreach(var entry in this.ChangeTracker.Entries()
.Where(e => e.Properties.Any(p => p.Metadata.Name == "UpdateDateTime")
&& e.State != Microsoft.EntityFrameworkCore.EntityState.Added))
{
entry.Property("UpdateDateTime").CurrentValue = DateTime.Now;
}
return base.SaveChanges();
}
Small detail: I make sure that when an entity is inserted the database defaults set both fields (see above: ValueGeneratedOnAdd(), and hence the exclusion of added entities) so there won't be confusing differences caused by client clocks being slightly off. I assume that updating will always be well later.
And to set IsDeleted you could add this method to the context:
public void MarkForDelete<T>(T entity)
where T : class
{
var entry = this.Entry(entity);
// TODO: check entry.State
if(entry.Properties.Any(p => p.Metadata.Name == "IsDeleted"))
{
entry.Property("IsDeleted").CurrentValue = true;
}
else
{
entry.State = Microsoft.EntityFrameworkCore.EntityState.Deleted;
}
}
...or turn to one of the proposed mechanisms out there to convert EntityState.Deleted to IsDeleted = true.
You can always generate an initial migration for the model and manually rearrange the column order in the Migration.
Here is the open issue tracking support for explicit column ordering in EF Core: https://github.com/aspnet/EntityFrameworkCore/issues/10059
Also see this question and answer on using Shadow Properties and Query Filters for soft deletes. EF Core: Soft delete with shadow properties and query filters
Well, I have the following model structure: I have one class - DatabaseEntity which is basically
public class DatabaseEntity
{
public int Id { get; set; }
}
so each entity like product, category etc will inherit DatabaseEntity and have Id property. Also I have typical EntityFramework repository class with InsertOrUpdate method:
private readonly DbContext _database;
public void InsertOrUpdate<TObject>(TObject entity) where TObject : DatabaseEntity
{
if(entity.Id == default(int))
{
// New entity
DbSet<TObject>().Add(entity);
}
else
{
// Existing entity
_database.Entry(entity).State = EntityState.Modified;
}
_database.SaveChanges();
}
Then I download from eBay via eBay api list of categoies I have to add to database. Basically category is:
public class EbayCategory : DatabaseEntity
{
// It has Id since it inherits DatabaseEntity
public string Name { get; set; }
// ... some other properties
}
But, the problem is, when I download those categories I download and their Id properties, which, of course, already have values. And when I try to save them to database like:
public void UpdateCategories(IEnumerable<EbayCategory> newCategories)
{
foreach (var newCategory in newCategories)
{
_repository.InsertOrUpdate(newCategory);
}
}
I face some issues... First of all, entity.Id != default(int) because it has value, so repository tries to update this entity, instead of adding, but it is not in the database or context so it throws the following exception:
System.Data.Entity.Infrastructure.DbUpdateConcurencyException
"Store update, insert, or delete statement affected an unexpected number of rows (0). Entities may have been modified or deleted since entities were loaded. Refresh ObjectStateManager entries."
... because it thinks that someone else deleted entity which I am trying to update. How can I save this InsertOrUpdate logic, since a lot of projects are based on it, and be able to add items (EbayCategories) with primary key (Id) to database and then update/delete them like other entities without discarding EbayCategory.Id value?
To allow you to manually generate Ids you need a class that has a manually generated ID - so it cannot inherit from DatabaseEntity
public class EbayCategory
{
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int Id { get; set; }
public string Name { get; set; }
// ... some other properties
}
Now you will need a different InsertOrUpdate to handle entities that have manually generated keys:
public void InsertOrUpdate(EbayCategory entity)
{
if(Find(entity.ID == null)
{
// New entity
DbSet<EbayCategory>().Add(entity);
}
else
{
// Existing entity
_database.Entry(entity).State = EntityState.Modified;
}
_database.SaveChanges();
}
Colin's answer above quite correctly shows how to achieve this setting using data annotations.
But in the presented problem the entity is a subclass so you can't add the annotation without changing the entity class.
There is an alternative configuration method: Fluent Configuration. Here's my example using an EntityTypeConfiguration class:
public class LookupSchoolsConfiguration : EntityTypeConfiguration<LookupSchools>
{
public LookupSchoolsConfiguration()
{
Property(l => l.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);
}
}
You can also add configuration directly to the modelBuilder as per this post: https://stackoverflow.com/a/4999894/486028
This question, asked a year ago, is similar:
Does the Entity Framework 4 support generators for id values like NHibernate?
But what I'd like to know is if the code first CTP adds support for identity generation strategies. If not, does anyone know a good extension point in EF to implement something similar?
I'm currently working with model classes which use GUID as the identifier. When inserting using EF they retain their Guid.Empty initial values. I know that you can set a default value for the column in the DB to newid() but that defeats the purpose of client-side identity generation.
Is Entity Framework just not mature enough to be used in a distributed, disconnected system?
No, Entity framework code-first is still just nice wrapper around EFv4. There are no NHibernate like generators. If you want client side Id generator you will have to override SaveChanges in derived DbContext and implement your own logic of assigning Ids to new entities.
Edit:
Some high level example:
public class Context : DbContext
{
// Helper for example
// DO NOT USE IN REAL SCENARIOS!!!
private static int i = 0;
public DbSet<MyEntity> MyEntities { get; private set; }
public Context()
: base("connection")
{
MyEntities = Set<MyEntity>();
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<MyEntity>().HasKey(e => e.Id);
// Turn off autogeneration in database
modelBuilder.Entity<MyEntity>()
.Property(e => e.Id)
.HasDatabaseGeneratedOption(HasDatabaseGeneratedOption.None);
// Other mapping
}
public override int SaveChanges()
{
foreach (var entry in ChangeTracker.Entries<MyEntity>()
.Where(e => e.State == EntityState.Added))
{
// Here you have to add some logic to generate Id
// I'm using just static field
entry.Entity.Id = ++i;
}
return base.SaveChanges();
}
}
public class MyEntity
{
public int Id { get; set; }
// Other properties
}
No.
Mine Entity Framework 4.1.10715 installed by NuGet.
maybe you could use attribute
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id {get;set;}
please see (The full list of annotations supported in EF 4.1 : CTRL+F in page) here.
[Key]
public string Id { get; set; }
and then use new GUID ToString
Units.Add( new Unit(){Id=Guid.NewGuid().ToString(), Name="123"});