Handling dependent entities when deleting the principal with Entity Framework 5 - entity-framework

Here's the situation in its most simplified form using the EF5 Code-First approach:
public abstract class EntityBase<PK>
{
public PK ID { get; set; }
}
public class Country : EntityBase<string>
{
public string Name { get; set; }
}
public class Address : EntityBase<int>
{
[Required]
public string CountryID { get; set; }
public Country Country { get; set; }
// ... other address properties ...
}
The one-to-many relationship between Address and Country is set up with no cascade-delete like so:
modelBuilder.Entity<Address>()
.HasRequired(a => a.Country)
.WithMany()
.HasForeignKey(a => a.CountryID)
.WillCascadeOnDelete(false);
Finally, I have a generic base repository class with CRUD methods that call SaveChanges on the underlying DbContext to commit data changes atomically. E.g.:
public class EFRepository<T, PK> : IRepository<T, PK> where T : EntityBase<PK>
{
//
// ... other methods ...
//
public virtual void Delete(T instance)
{
// ... trigger validations, write to log, etc...
_dbContext.Set<T>().Remove(instance);
try
{
_dbContext.SaveChanges();
}
catch(Exception ex)
{
// ... handle the error ...
}
}
}
Part 1:
Scenario:
var countryRepo = new EFRepository<Country>();
var country = countryRepo.Save(new Country() { ID="??", Name="Test Country" });
var addressRepo = new EFRepository<Address>();
var address = addressRepo.Save(new Address() { Country=country });
countryRepo.Delete(country);
This should fail due to the existence of a dependent Address. However, afterwards the address ends up with a null in CountryID, which is invalid because Address.CountryID is required, so subsequent SaveChanges calls throw a validation exception unless the address is detached.
I expected that when an object is deleted, EF5 will be smart enough to first check for any cascade-delete constraints like the one above and, failing to find any, then proceed to delete the data. But exactly the opposite seems to be the case.
Is this a normal behaviour or am I doing something wrong?
Part 2:
Following a failed SaveChanges call, some Addresses are now in an invalid state in my DbContext and need to be restored to their original values. Of course, I can always do so explicitly for each entity type (Country, State, Order, etc.) by creating specialized repository classes and overriding Delete, but it smells big time. I'd much rather write some general purpose code to gracefully recover related entities after a failed SaveChanges call.
It would require interrogating DbContext to get all relationships in which an entity (e.g. Country) is the principal, regardless of whether or not its class defines navigational properties to dependent entities.
E.g. Country has no Addresses property, so I need to somehow find in DbContext the definition of the one-to-many relationship between Country and Address and use it to restore all related Addresses to their original values.
Is this possible?

Answering my own question in Part 2:
Here is my approach to checking for related dependents when deleting an entity on the principal end of a many-to-one relationship and where dependents are NOT exposed as a navigation collection in the principal (e.g. class Address has a Country property, but class Country doesn't have an Addresses collection).
DbContext
Add the following method to the context class:
/// <summary>
/// Returns an array of entities tracked by the
/// context that satisfy the filter criteria.
/// </summary>
public DbEntityEntry[] GetTrackedEntities<T>(
Expression<Func<DbEntityEntry<T>, bool>> filterCriteria)
where T : class
{
var result = new List<DbEntityEntry>();
var doesItMatch = filterCriteria.Compile();
foreach (var entry in this.ChangeTracker.Entries<T>())
{
if (doesItMatch(entry))
result.Add(entry);
}
return result.ToArray();
}
Repositories
Create a repository for each class that has some dependencies, override the Delete method and use the new GetTrackedEntities<T> method to get all related dependents and either:
explicitly delete them if they are cascade-deletable in code
detach them from the context if they are cascade-deletable in the DB itself
throw an exception if they are NOT cascade-deletable.
Example of the latter case:
public class EFCountryRepository :
EFReadWriteRepository<Country, string>,
ICountryRepository
{
public override void Delete(Country instance)
{
// Allow the Country to be deleted only if there are no dependent entities
// currently in the context that are NOT cascade-deletable.
if (
// are there any Regions in the context that belong to this Country?
_dbContext.GetTrackedEntities<Region>(e =>
e.Entity.CountryID == instance.ID ||
e.Entity.Country == instance).Length > 0
||
// are there any Addresses in the context that belong to this Country?
_dbContext.GetTrackedEntities<Address>(e =>
e.Entity.CountryID == instance.ID ||
e.Entity.Country == instance).Length > 0
)
throw new Exception(String.Format(
"Country '{0}' is in use and cannot be deleted.", instance.ID));
base.Delete(instance);
}
// ... other methods ...
}
Example of a case where cascade-deleting will be done by the DB itself, so all we need to do is detach the dependents from the context:
public class EFOrderRepository :
EFReadWriteRepository<Order, string>,
IOrderRepository
{
public override void Delete(Order instance)
{
foreach (var orderItem in _dbContext.GetTrackedEntities<OrderItem>(e =>
e.Entity.OrderID == instance.ID ||
e.Entity.Order == instance))
{
_dbContext.Entry(orderItem).State = System.Data.EntityState.Detached;
}
base.Delete(instance);
}
// ... other methods ...
}
Hope someone will find this solution helpful.

Related

Retrieve child entities from CrudAppService in abp.io using .Net 5 EF

I'm using the latest version of ABP from abp.io and have two entities with a many-many relationship. These are:
public class GroupDto : AuditedEntityDto<Guid>
{
public GroupDto()
{
this.Students = new HashSet<Students.StudentDto>();
}
public string Name { get; set; }
public bool IsActive { get; set; }
public virtual ICollection<Students.StudentDto> Students { get; set; }
}
and
public class StudentDto : AuditedEntityDto<Guid>
{
public StudentDto()
{
this.Groups = new HashSet<Groups.GroupDto>();
}
public string Name { get; set; }
public bool IsActive { get; set; }
public virtual ICollection<Groups.GroupDto> Groups { get; set; }
}
I set up the following test to check that I am retrieving the related entities, and unfortunately the Students property is always empty.
public async Task Should_Get_List_Of_Groups()
{
//Act
var result = await _groupAppService.GetListAsync(
new PagedAndSortedResultRequestDto()
);
//Assert
result.TotalCount.ShouldBeGreaterThan(0);
result.Items.ShouldContain(g => g.Name == "13Ck" && g.Students.Any(s => s.Name == "Michael Studentman"));
}
The same is true of the equivalent test for a List of Students, the Groups property is always empty.
I found one single related answer for abp.io (which is not the same as ABP, it's a newer/different framework) https://stackoverflow.com/a/62913782/7801941 but unfortunately when I add an equivalent to my StudentAppService I get the error -
CS1061 'IRepository<Student, Guid>' does not contain a definition for
'Include' and no accessible extension method 'Include' accepting a
first argument of type 'IRepository<Student, Guid>' could be found
(are you missing a using directive or an assembly reference?)
The code for this is below, and the error is being thrown on the line that begins .Include
public class StudentAppService :
CrudAppService<
Student, //The Student entity
StudentDto, //Used to show students
Guid, //Primary key of the student entity
PagedAndSortedResultRequestDto, //Used for paging/sorting
CreateUpdateStudentDto>, //Used to create/update a student
IStudentAppService //implement the IStudentAppService
{
private readonly IRepository<Students.Student, Guid> _studentRepository;
public StudentAppService(IRepository<Student, Guid> repository)
: base(repository)
{
_studentRepository = repository;
}
protected override IQueryable<Student> CreateFilteredQuery(PagedAndSortedResultRequestDto input)
{
return _studentRepository
.Include(s => s.Groups);
}
}
This implements this interface
public interface IStudentAppService :
ICrudAppService< // Defines CRUD methods
StudentDto, // Used to show students
Guid, // Primary key of the student entity
PagedAndSortedResultRequestDto, // Used for paging/sorting
CreateUpdateStudentDto> // Used to create/update a student
{
//
}
Can anyone shed any light on how I should be accessing the related entities using the AppServices?
Edit: Thank you to those who have responded. To clarify, I am looking for a solution/explanation for how to access entities that have a many-many relationship using the AppService, not the repository.
To aid with this, I have uploaded a zip file of my whole source code, along with many of the changes I've tried in order to get this to work, here.
You can lazy load, eagerly load or configure default behaviour for the entity for sub-collections.
Default configuration:
Configure<AbpEntityOptions>(options =>
{
options.Entity<Student>(studentOptions =>
{
studentOptions.DefaultWithDetailsFunc = query => query.Include(o => o.Groups);
});
});
Eager Load:
//Get a IQueryable<T> by including sub collections
var queryable = await _studentRepository.WithDetailsAsync(x => x.Groups);
//Apply additional LINQ extension methods
var query = queryable.Where(x => x.Id == id);
//Execute the query and get the result
var student = await AsyncExecuter.FirstOrDefaultAsync(query);
Or Lazy Load:
var student = await _studentRepository.GetAsync(id, includeDetails: false);
//student.Groups is empty on this stage
await _studentRepository.EnsureCollectionLoadedAsync(student, x => x.Groups);
//student.Groups is filled now
You can check docs for more information.
Edit:
You may have forgotten to add default repositories like:
services.AddAbpDbContext<MyDbContext>(options =>
{
options.AddDefaultRepositories();
});
Though I would like to suggest you to use custom repositories like
IStudentRepository:IRepository<Student,Guid>
So that you can scale your repository much better.

Entity framework don't delete records but fill column

I would like to change the way EF works with deleting records.
Instead of deleting the row in the database it should fill a column (GCColumn or so).
When retrieving data it should always filter on GCColumn IS NULL + the filter you apply.
Anyone know if this is achievable and how ?
I addition to my answer above, consider the case in which many or even all of your entities have this GCColumn.
You could start with a base entity for these pseudo-deletable entities:
public abstract class PseudoDeletable
{
public DateTime GCColumn { get; set;}
}
and have entities defined as:
public class Order : PseudoDeletable
{
public int Id { get; set; }
public int ProductId { get; set; }
public DateTime OrderDate { get; set; }
// etc.
}
Then, you could create a generic base repository
public class RepositoryBase<TEntity> where TEntity : PseudoDeletable
{
protected IDbSet<TEntity> DbSet { get; }
public RepositoryBase()
{
DbSet = context.Set<TEntity>();
}
private Expression<Func<TEntity, bool>> RemoveDeleted
{
get { return e => e.GCColumn == null; }
}
public virtual IEnumerable<TEntity> GetAll(Expression<Func<TEntity, bool>> expression)
{
expression = expression.And(RemoveDeleted);
return DbSet.Where(expression).ToList();
}
}
and have derived repositories, like:
public class OrderRepository : RepositoryBase<Order>
{
}
The GetAll method can then be called like this:
new orderRepository().GetAll(x => x.ProductId == 1);
and it will just return orders that have not been deleted.
Please note that you'll have an issue with entity includes for related records: how to include only un-deleted related entities, but that is a consequence of you desire to keep 'deleted' records in the database.
In one project we use the repository pattern for database access and each entity has its own repository.
It is a multi-tenant database and we use the type of filter you are looking for to filter entities accessible to the current user, not to filter for a delete flag, but the method could be used analogously.
Each repository that needs filtering, gets a filter method:
private Expression<Func<Order, bool>> RemoveDeleted
{
get
{
return order => order.GCColumn == null;
}
}
Then, add an expression to each repository method, like:
public override IEnumerable<Order> GetAll(Expression<Func<Order, bool>> expression)
{
expression = expression.And(RemoveDeleted);
return DbSet.Where(expression).ToList();
}
(The extension method Add comes from a set of ExpressionExtensions.)
Now, you can use expressions like:
orderRepository.GetAll(x => x.ProductId == productId);
and
orderRepository.GetAll(x => x.OrderDate >= DateTime.Now.AddMonths(-1));
So now you business logic can have many methods using the same GetAll() methods, with different filters, but doesn't have to care about 'deleted' entities. But you are still responsible for creating a correct filter for each repository method.
If the delete flag is not in all entities, but the delete status is registered in another entity, you can do the following:
private Expression<Func<Order, bool>> RemoveDeleted
{
get
{
return orderLine => orderLine.Order.GCColumn == null;
}
}
In this example orders are deleted in whole, not individual lines in it.

Changing EF6 source code for conversion of short to bool

What is the feasibility of modifying the mapping code to convert a short of value zero or non-zero to false or true, if the boolean destination property is marked with an attribute in the POCO model?
I mean, this is supposed to be one of the advantages of EF being open sourced, and would be for in house use only.
Any tips on where in the code I would look would be appreciated, but this question is really more general and I'd like to hear anything anyone has to say on this.
With regard to the General comments please.
I dont know to make the EF change, but dealing with similar issues is not an uncommon issue in EF.
Not all standard types are supported by EF.
You can have a helper field in your POCO class.
So one field is the actual DB field, but no used outside of POCO.
The help field is NOTMAPPED or ignored in fluent API.
You access the DB via you helper and execute any required casting.
A simple example. Or the reverse if I got helper and DB field types back to front.
[NotMapped]
public virtual bool IsVisible { set; get; } // Helper Field NOT on DB
public int Test { get { return IsVisible ? 1 : 0; } // on DB, but set and get via helper only.
set { IsVisible = (value != 0); } }
Edit: Power Fluent API
Here is a snippet that outlines how you have code that runs for every mapped poco in a consistent way.
public class MyDbContext : DbContext
// model building, set breakpoint so you know when this is triggered
// it is important this ISNT called everytime, only on model cache.
// in my case that is app pool recycle.
protected override void OnModelCreating(DbModelBuilder modelBuilder)
// use the CONFIG add feature to better organize and allow use of inheritance when mapping
// I will use snippets and statics to keep it simple.
modelBuilder.Configurations.Add(XYZMap.Map()); // POCO map
modelBuilder.Configurations.Add(ABCMAP.Map()); // poco map
modelBuilder.Configurations.Add(XXXMap.MAP()); // poco map
// etc for your POCO set
// Note, no need to declare DBset<xyz> XYZs {get;set;} !!!!
public static class XYZMap {
public static BaseEntityIntConfiguration<PocoXYZ> Map() {
//see return object !
var entity = new BaseEntityLongConfiguration<PocoXYZ>();
//entity.Property()... // map away as usual POCO specifc
///entity.HasRequired()...// property and relationships as required
// do nothing for default
return entity;
}
}
}
// all tables with int key use this base config. do it once never again
public class BaseEntityIntConfiguration<T> : BaseEntityConfiguration<T> where T : BaseObjectInt {
public BaseEntityIntConfiguration(DatabaseGeneratedOption DGO = DatabaseGeneratedOption.Identity) {
// Primary Key
this.HasKey(t => t.Id);
// Properties
//Id is an int allocated by DB
this.Property(t => t.Id).HasDatabaseGeneratedOption(DGO); // default to db generated
// optimistic lock is also added here, Specific to out poco design
this.Property(t => t.RowVersion)
.IsRequired()
.IsFixedLength()
.HasMaxLength(8)
.IsRowVersion();
// any other common mappings/ rules ??
}
}
public class BaseEntityConfiguration<T> : EntityTypeConfiguration<T> where T : BaseObject {
public BaseEntityConfiguration() {
this.ApplyAttributeRules(); // <<<<< Here is where I apply SYSTEM WIDE rules
}
}
public static void ApplyAttributeRules<T>(this EntityTypeConfiguration<T> entity) where T : BaseObject {
// so this will be called for each mapped type
foreach (var propertyInfo in typeof (T).GetProperties()) {
// I use reflection to look for properties that meet certain criteria.
// eg string. I want as NVARCHAR 4000 not NVCAHR max so i can index it.
if (propertyInfo.UnderLyingType().FullName == "System.String") {
SetStringLength(BosTypeTool.StringLengthIndexable, propertyInfo.Name, entity);
continue;
}
SetStringLength(4000, propertyInfo.Name, entity);
}
}
private static void SetStringLength<TModelPoco>(int length, string propertyName,
EntityTypeConfiguration<TModelPoco> entity) where TModelPoco : BaseObject {
var propLambda = DynamicExpression.ParseLambda<TModelPoco, String>(propertyName);
entity.Property(propLambda).HasMaxLength(length);
// dynamic library from Microsoft.... http://msdn.microsoft.com/en-US/vstudio/bb894665.aspx
}
// get underlying type incase it is nullable
public static Type UnderLyingType(this PropertyInfo propertyInfo) {
return Nullable.GetUnderlyingType(propertyInfo.PropertyType) ?? propertyInfo.PropertyType;
}

How to create generic EF Insert method?

I'd like to create a generic C# class with a method that will add a row to a database using Entity Framework.
I have one table called Address. I've written the following code to add an address to the database:
public class AddressExchange
{
public int Insert(Address address)
{
using (var db = new DemoWebEntities())
{
//db.AddObject("Address", address);
db.Addresses.AddObject(address);
db.SaveChanges();
return address.Id;
}
}
}
I would like to write a generic class that will perform this operation for any entity in my EDMX. I think that it should look something like this:
public class EntityExchange<T, KeyType>
{
public KeyType Insert(T t)
{
using (var db = new DemoWebEntities())
{
// The entity set name might be wrong.
db.AddObject(typeof(T).Name, t);
// EF doesn't know what the primary key is.
return t.Id;
}
}
}
I think it may be possible to use the AddObject method to add the object to the database, but the entityset name is not necessarily the same as the type name, especially if it has been pluralized!
I also want to return the primary key to the caller, but I don't know how to tell which field contains the primary key.
I have a generic InsertOrUpdate method in a generic repository that also ensures proxies are created. (Proxies are required to support lazy loading and if you create an entity using "new", then proxies are not created). See the question here
public class RepositoryBase<T> : IRepository<T> where T : ModelBase
{
public virtual T InsertOrUpdate(T e)
{
DbSet<T> dbSet = context.Set<T>();
//Generate a proxy type to support lazy loading
T instance = dbSet.Create();
DbEntityEntry<T> entry;
if (e.GetType().Equals(instance.GetType()))
{
//The entity being added is already a proxy type that
//supports lazy loading just get the context entry
entry = context.Entry(e);
}
else
{
//The entity being added has been created using the "new" operator.
//Attach the proxy
//Need to set the ID before attaching or we get
//The property 'ID' is part of the object's key
//information and cannot be modified when we call SetValues
instance.ID = e.ID;
entry = context.Entry(instance);
dbSet.Attach(instance);
//and set it's values to those of the entity
entry.CurrentValues.SetValues(e);
e = instance;
}
entry.State = e.ID == default(int) ?
EntityState.Added :
EntityState.Modified;
return e;
}
}
public abstract class ModelBase
{
public int ID { get; set; }
}
Note that all the models inherit ModelBase so that handles the ID issue and I return the entity rather than just the ID. That is probably not strictly necessary since a reference to the entity is passed in and EF performs fixup on the ID anyway so you can always access it from the refernce passed in.
This might be reliant on a particular version on Entity framework however this is how I do it
public void Create(T entity)
{
using (var db = new DemoWebEntities())
{
db.Set<T>().Add(entity);
}
}
For the primary key issue, can you use partial classes to make your entities implement an interface, something like this:
public interface IEntity
{
Guid PrimaryKey { get; }
}
Your entity classes would then return the appropriate value:
public partial class EntityType : IEntity
{
public Guid PrimaryKey
{
get
{
return this.WhateverId; // Return the primary key
}
}
}
Then, constrain your method to only accept IEntity:
public class EntityExchange<T, KeyType> where T : IEntity
And finally return the primary key after the insert:
return t.PrimaryKey;
May be it can help you.
public T Add(T model)
{
using (BigConceptEntities entity = new BigConceptEntities())
{
entity.Set<T>().Add(model);
entity.SaveChanges();
return model;
}
}

Decoupling Entity Framework from my POCO classes

I'm dynamically creating my DbContext by iterating over any entities that inherit from EntityBase and adding them to my Context:
private void AddEntities(DbModelBuilder modelBuilder)
{
var entityMethod = typeof(DbModelBuilder).GetMethod("Entity");
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
{
var entityTypes = assembly.GetTypes()
.Where(x => x.IsSubclassOf(typeof(EntityBase)) && !x.IsAbstract);
foreach (var type in entityTypes)
{
dynamic entityConfiguration = entityMethod.MakeGenericMethod(type).Invoke(modelBuilder, new object[] { });
EntityBase entity = (EntityBase)Activator.CreateInstance(type);
//Add any specific mappings that this class has defined
entity.OnModelCreating(entityConfiguration);
}
}
}
That way, I can have many namespaces but just one generic repository in my base namespace that's used everywhere. Also, in apps that make use of multiple namespaces, the base repository will already be setup to use all the entities in all the loaded namespaces. My problem is, I don't want to make EntityFramework.dll a dependency of every namespace in the company. So I'm calling OnModelCreating and passing the EntityTypeConfiguration to the class so it can add any mappings. This works fine and here's how I can add a mapping to tell the model that my "Description" property comes from a column called "Descriptor":
class Widget... {
public override void OnModelCreating(dynamic entity)
{
System.Linq.Expressions.Expression<Func<Widget, string>> tmp =
x => x.Description;
entity.Property(tmp).HasColumnName("Descriptor");
}
The good thing is, my entity class has no reference to EF, this method is only called once, when the context is created and if we scrap EF and go to something else in the future, my classes won't have all sorts of attributes specific to EF in them.
The problem is, it's super ugly. How can I let the model know about column mappings and keys in a simpler way than creating these Expressions to get properties to map without hard coding references to EF all over my poco classes?
You could define your own Attributes and use these to control the configuration within OnModelCreating(). You should be able to gain (using reflection) all the details you need for column mapping in one linq query a second query for the creation of the key.
public class DatabaseNameAttribute : Attribute
{
private readonly string _name;
public DatabaseNameAttribute(string name)
{
_name = name;
}
public string Name
{
get
{
return _name;
}
}
}
public class KeySequenceAttribute : Attribute
{
private readonly int _sequence;
public KeySequenceAttribute(int sequence)
{
_sequence = sequence;
}
public int Sequence
{
get
{
return _sequence;
}
}
}
[DatabaseName("BlogEntry")]
public class Post
{
[DatabaseName("BlogId")]
[KeySequence(1)]
public int id { get; set; }
[DatabaseName("Description")]
public string text { get; set; }
}