C# Entity Framework Generic class -> reload all child entities - c#

I currently have a multi one-to-many relationship hierarchy database tblProjects->tblLines->tblGroups->tblStations etc. And an Entity framework 6 model.
These entity framework classes all implement a base class "tblBase":
public abstract class TblBase : INotifyPropertyChanged
{
private int _id;
public int ID
{
get
{
return _id;
}
set
{
_id = value;
NotifyPropertyChanged();
}
}
private Nullable<int> _coid;
public Nullable<int> COID
{
get
{
NotifyPropertyChanged();
return _coid;
}
set
{
_coid = value;
NotifyPropertyChanged();
}
}
private string _name;
public string Name
{
get
{
return _name;
}
set
{
_name = value;
NotifyPropertyChanged();
}
}
I have a treeview that allows me to select any node as the parent type, and currently I have a method for each type that allows me to reload all the child entities.
I would like to see how this could be made generic:
private async static Task<bool> RefreshLinesAsync(LocalUser ThisUser, ProjectEntities DBContext, object Entity)
{
List<object> NonExistingNodes = new List<object>();
var bContinue = false;
var PassedEntity = Entity as TblBase;
//Scan through all DB child entities and reload their DB values
foreach (var SubEntity in DBContext.tblLines.Where(x => x.ProjectID == PassedEntity.ID).ToList())
{
await DBContext.Entry(SubEntity).ReloadAsync().ContinueWith(x =>
{
if (!x.IsFaulted)
{
if ((SubEntity.COID.GetValueOrDefault() != 0) && (SubEntity.COID.GetValueOrDefault() != ThisUser.ID))
NotifyCOIDConflict(SubEntity, new CheckedOutArgs()
{
ConflictCOID = SubEntity.COID.GetValueOrDefault()
});
bContinue = true;
}
}, TaskScheduler.FromCurrentSynchronizationContext());
if (bContinue)
//Continue to child entities method
await RefreshGroupsAsync(ThisUser, DBContext, SubEntity);
}
return true;
}
private async static Task<bool> RefreshGroupsAsync(LocalUser ThisUser, ProjectEntities DBContext, object Entity)
{
List<object> NonExistingNodes = new List<object>();
var bContinue = false;
var PassedEntity = Entity as TblBase;
foreach (var SubEntity in DBContext.tblGroups.Where(x => x.LineID == PassedEntity.ID).ToList())
{
await DBContext.Entry(SubEntity).ReloadAsync().ContinueWith(x =>
{
if (!x.IsFaulted)
{
if ((SubEntity.COID.GetValueOrDefault() != 0) && (SubEntity.COID.GetValueOrDefault() != ThisUser.ID))
NotifyCOIDConflict(SubEntity, new CheckedOutArgs()
{
ConflictCOID = SubEntity.COID.GetValueOrDefault()
});
bContinue = true;
}
}, TaskScheduler.FromCurrentSynchronizationContext());
if (bContinue)
await RefreshStationsAsync(ThisUser,DBContext, SubEntity);
}
return true;
}
The only method I can see useful is Set(), although it does not provide a Where() method, which is critical since I do not want to retrieve the entire table.

You can make your functions generic. They maybe like this one:
private async static Task<bool> RefreshLinesAsync<TEntity>(LocalUser ThisUser, ProjectEntities DBContext, TEntity Entity) where TEntity : TblBase
{
List<TEntity> NonExistingNodes = new List<TEntity>();
var bContinue = false;
var PassedEntity = Entity as TblBase;
foreach (var SubEntity in DBContext.Set<TEntity>().Where(x => (x as TblBase).ProjectID == PassedEntity.ID).ToList()) {
//Your other code here...
}
}
The where clause in function definition, make you sure that this method can be called only with subclasses of TblBase.
EDIT:
I forgot to mention that you need to cast SubEntity as TblBase inside foreach loop to use it...
EDIT (in response of comments):
If you need to get all TblBase subclasses from your entity, you cannot make your function so generic if you keep them in separate tables: It will became hardly mantainable when you have to add more subclasses.
I suggest you to use a single table through Table Per Hierarchy (see this article in MSDN) changing TblBase from abstract to concrete class, then you can get all of them this way:
var allSubClassEntities = DBContext.Set<TblBase>();

Related

Reflection, set.GetValue(context, null) always return null

I'm trying to get a property from an object by reflection.
public class CosmosDbSet<TEntity> : DbSet<TEntity> where TEntity : class, IEntity<string>
{
public string Name { get; }
//...
);
}
public class SKCosmosDbContext : CosmosDBContext
{
public CosmosDbSet<Item> Items { get; }
public SKCosmosDbContext ()
{
Items = new CosmosDbSet<Item>(
this,
"Items"
);
}
//...
}
public abstract class CosmosDBContext : DbContext
{
public async Task EnsureContainersExistAsync()
{
var sets = GetType().GetProperties()
.Where(pi => pi.PropertyType.IsGenericType
&& pi.PropertyType.GetGenericTypeDefinition().Equals(typeof(CosmosDbSet<>))
);
foreach (var set in sets)
{
var value = set.GetValue(this, null); // => value is always null
//...
}
}
}
public static class DbInitializer
{
public async static Task InitializeAsync(IServiceProvider services, ILogger logger)
{
var dbContext = services.GetRequiredService<SKCosmosDbContext>();
await dbContext.EnsureContainersExistAsync();
}
}
As you can see, the property Items from SKCosmosDbContext has been found but, I can't have access to it.
How to have access to the property using reflection?
So basically I see problem in using .GetGenericTypeDefinition() call.
If you do more detailed debug you could see that it returns enumerable with next content:
To get what you want you could use pi.PropertyType.GetGenericArguments()[0] and than use its return value to equal it in your linq query.
ex.
I used dummy types just for sake of example
Your problem could be related also with this one: Get type of generic list
TL;DR Example works after changing query to:
var sets = db.GetType().GetProperties()
.Where(pi => pi.PropertyType.IsGenericType
&& pi.PropertyType.GetGenericArguments()[0].Equals(typeof(...))
);

audit trail with entity framework

I have fields for audit trail in each table (InsertedBy, InsertedDate, UpdatedBy and UpdatedDate), I build solution to reduce redundant before by override savechange():
public override int SaveChanges()
{
foreach (var entry in ChangeTracker.Entries().Where(e =>
e.State == System.Data.Entity.EntityState.Added || e.State == System.Data.Entity.EntityState.Modified))
{
Auditing.ApplyAudit(entry, User);
}
return base.SaveChanges();
}
public class Auditing
{
public static void ApplyAudit(DbEntityEntry entityEntry, int User)
{
Type type = entityEntry.Entity.GetType();
if (entityEntry.State.ToString() == "Added")
{
if (type.GetProperty("InsertedBy") != null)
{
entityEntry.Property("InsertedBy").CurrentValue = User;
}
if (type.GetProperty("InsertedDate") != null)
{
entityEntry.Property("InsertedDate").CurrentValue = DateTime.Now;
}
}
else if (entityEntry.State.ToString() == "Modified")
{
if (type.GetProperty("InsertedBy") != null)
{
entityEntry.Property("InsertedBy").IsModified = false;
}
if (type.GetProperty("InsertedDate") != null)
{
entityEntry.Property("InsertedDate").IsModified = false;
}
if (type.GetProperty("UpdatedBy") != null)
{
entityEntry.Property("UpdatedBy").CurrentValue = User;
}
if (type.GetProperty("UpdatedDate") != null)
{
entityEntry.Property("UpdatedDate").CurrentValue = DateTime.Now;
}
}
}
}
the question is:
is using reflection within each entity before modified or added waste in memory and performance ? if yes is there is best practice for this ?
is this another code snippet better in performance or just use reflection also?
public static void ApplyAudit(DbEntityEntry entityEntry, long User)
{
if (entityEntry.State.ToString() == "Added")
{
entityEntry.Property("InsertedBy").CurrentValue = User;
entityEntry.Property("InsertedDate").CurrentValue = DateTime.Now;
}
else if (entityEntry.State.ToString() == "Modified")
{
entityEntry.Property("InsertedBy").IsModified = false;
entityEntry.Property("InsertedDate").IsModified = false;
entityEntry.Property("UpdatedBy").CurrentValue = User;
entityEntry.Property("UpdatedDate").CurrentValue = DateTime.Now;
}
}
is entityEntry.Property("InsertedBy") uses reflection ?
Reflection is slow (slow is subjective) and if you want to avoid it, then you need to get rid of such code as below:
Type type = entityEntry.Entity.GetType();
if (type.GetProperty("InsertedBy") != null)
Even if it was not slow, the code above is still "buggy" because a programmer may mistakenly write InsertBy instead of InsertedBy. This can easily be avoided with help from the compiler using the approach below.
Use an interface and implement it in all entities that require audit.
public interface IAuditable
{
string InsertedBy { get; set; }
// ... other properties
}
public class SomeEntity : IAuditable
{
public string InsertedBy { get; set; }
}
public class Auditor<TAuditable> where TAuditable : IAuditable
{
public void ApplyAudit(TAuditable entity, int userId)
{
// No reflection and you get compiler support
if (entity.InsertedBy == null)
{
// whatever
}
else
{
// whatever
}
}
}
As mentioned in the comments, you will get compiler support and reflection is not used anymore. I would even go a step further and not pass the int userId. I will bring the code for figuring out the userId and put it in this class. That way the class is self sufficient and clients do not need to provide it this information.
Usage:
var e = new SomeEntity();
var auditor = new Auditor<SomeEntity>();
auditor.ApplyAudit(e, 1); // 1 is userId, I am just hardcoding for brevity
Or use it from your context:
public override int SaveChanges()
{
var auditables = ChangeTracker.Entries().Where(e =>
e.State == System.Data.Entity.EntityState.Added || e.State == System.Data.Entity.EntityState.Modified)
.OfType<IAuditable>();
var auditor = new Auditor<IAuditable>();
foreach (var entry in auditables)
{
// 1 is userId, I am just hardcoding for brevity
auditor.ApplyAudit(entry, 1);
}
return base.SaveChanges();
}
This means that all entities who are auditable will need to implement the IAuditable interface. EF generates partial classes for your entities but do not modify those partial classes because the next time you run the custom tool, it will be wiped out.
Instead, create another partial class with the same name and implement the IAuditable.
public partial class SomeEntity : IAuditable {}
An even better approach is to create a custom T4 template so it creates the partial class with the code : IAuditable. Please see this article for how to do that.

Calling repository methods from another repository C#

I have a "DatabaseController" Class, which contains several repository classes, each for every table in a database, and they only perform crud operations. My issue is this.
When i delete an entry , i call the corresponding repository method. I also need to call some delete methods from other tables (repositories). What is the best approach to do this?
Example Code:
DatabaseController Class
public DatabaseController(){
this.dbContext = new WSATDbContext();
}
private IWSATGenericRepository<Restmethod> _restmethod ;
private IWSATGenericRepository<Soapservice> _soapservice ;
public IWSATGenericRepository<Restmethod> Restmethod
{
get
{
if (_restmethod == null)
{
_restmethod = new RestmethodRepository(dbContext);
}
return _restmethod;
}
}
public IWSATGenericRepository<Soapservice> Soapservice
{
get
{
if (_soapservice == null)
{
_soapservice = new SoapserviceRepository(dbContext);
}
return _soapservice;
}
}
Example Repository:
public class RestmethodRepository : IWSATGenericRepository<Restmethod>
{
public RestmethodRepository(DbContext dbContext)
{
if (dbContext == null)
throw new ArgumentNullException("Null DbContext");
this.dbContext = dbContext;
this.dbSet = dbContext.Set<Restmethod>();
}
private DbContext dbContext { get; set; }
private DbSet<Restmethod> dbSet { get; set; }
public Restmethod get(int id)
{
Restmethod restmethod = dbSet.Find(id) ;
dbContext.Entry(restmethod).Collection(s => s.methodkeywords).Load() ;
return restmethod ;
}
public Restmethod delete(int id)
{
var entity = this.get(id);
//I want to call some other repo methods here
dbContext.Entry(entity).State = EntityState.Deleted;
try
{
dbContext.SaveChanges();
}
catch (DbUpdateException e)
{
System.Diagnostics.Debug.WriteLine(e.StackTrace);
return null;
}
return entity;
}
}
}
EDIT: Is it good practice to pass the "DatabaseController" UOW class as an argument to each repository? Are there any theoretical problems with that? This is an academic project , so i'd like to stay as formal as possible.

Property not updated after SaveChanges (EF database first)

First of all, I would like to say that I read the related posts (notably EF 4.1 SaveChanges not updating navigation or reference properties, Entity Framework Code First - Why can't I update complex properties this way?, and Entity Framework 4.1 RC (Code First) - Entity not updating over association).
However, I could not solve my problem. I am quite new to Entity Framework so I guess I must have misunderstood those posts answers.
Anyway I would be really grateful is someone could help me understand because I am quite stuck.
I have two tables :
Person
Item with a nullable PersonId and a Type
An item can have an owner, or not.
Consequently, Person has an Items property which is an IEnumerable of Item.
A person can have one only Item by type.
If the person wants to change, he can replace his current item by any other of the same type in his items :
public class MyService
{
private PersonRepo personRepo = new PersonRepo();
private ItemRepo itemRepo = new ItemRepo();
public void SwitchItems(Person person, Guid newItemId)
{
using (var uof = new UnitOfWork())
{
// Get the entities
Item newItem = itemRepo.Get(newItemId);
Item oldItem = person.Items.SingleOrDefault(i => i.Type == newItem.Type)
// Update the values
newItem.PersonId = person.Id;
oldItem.PersonId = null;
// Add or update entities
itemRepo.AddOrUpdate(oldItem);
itemRepo.AddOrUpdate(newItem);
personRepo.AddOrUpdate(person);
uof.Commit(); // only does a SaveChanges()
}
}
}
Here is the repositories structure and the AddOrUpdate method :
public class PersonRepo : RepositoryBase<Person>
{
...
}
public class RepositoryBase<TObject> where TObject : class, IEntity
{
protected MyEntities entities
{
get { return UnitOfWork.Current.Context; }
}
public virtual void AddOrUpdate(TObject entity)
{
if (entity != null)
{
var entry = entities.Entry<IEntity>(entity);
if (Exists(entity.Id))
{
if (entry.State == EntityState.Detached)
{
var set = entities.Set<TObject>();
var currentEntry = set.Find(entity.Id);
if (currentEntry != null)
{
var attachedEntry = entities.Entry(currentEntry);
attachedEntry.CurrentValues.SetValues(entity);
}
else
{
set.Attach(entity);
entry.State = EntityState.Modified;
}
}
else
entry.State = EntityState.Modified;
}
else
{
entry.State = EntityState.Added;
}
}
}
}
This works pretty well and the old and the new items' PersonId properties are correctly updated in database.
However, if I check person.Items after the SaveChanges(), the old item still appears instead of the new one and I need it to be correct in order to update the page's controls values.
Although I read the posts with the same issue I could not resolve it...
I tried lots of things, notably calling entities.Entry(person).Collection(p => p.Items).Load() but got an exception each time I tried.
If somebody has any idea please feel free, I can add some more code if needed.
Thanks a lot !
EDIT : UnitOfWork
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Data;
using System.Data.Entity.Infrastructure;
using System.Data.Objects;
public class UnitOfWork : IDisposable
{
private const string _httpContextKey = "_unitOfWork";
private MyEntities _dbContext;
public static UnitOfWork Current
{
get { return (UnitOfWork)HttpContext.Current.Items[_httpContextKey]; }
}
public UnitOfWork()
{
HttpContext.Current.Items[_httpContextKey] = this;
}
public MyEntities Context
{
get
{
if (_dbContext == null)
_dbContext = new MyEntities();
return _dbContext;
}
}
public void Commit()
{
_dbContext.SaveChanges();
}
public void Dispose()
{
if (_dbContext != null)
_dbContext.Dispose();
}
}
Two solutions that worked
Solution 1 (reload from context after SaveChanges)
public partial class MyPage
{
private MyService service;
private Person person;
protected void Page_Load(object sender, EventArgs e)
{
service = new MyService();
person = service.GetCurrentPerson(Request.QueryString["id"]);
...
}
protected void SelectNewItem(object sender, EventArgs e)
{
Guid itemId = Guid.Parse(((Button)sender).Attributes["id"]);
service.SelectNewItem(person, itemId);
UpdatePage();
}
private void UpdatePage()
{
if (person != null)
person = service.GetCurrentPerson(Request.QueryString["id"]);
// Update controls values using person's properties here
}
}
public class MyService
{
private PersonRepo personRepo = new PersonRepo();
private ItemRepo itemRepo = new ItemRepo();
public void SwitchItems(Person person, Guid newItemId)
{
using (var uof = new UnitOfWork())
{
// Get the entities
Item newItem = itemRepo.Get(newItemId);
Item oldItem = person.Items.SingleOrDefault(i => i.Type == newItem.Type)
// Update the values
newItem.PersonId = person.Id;
oldItem.PersonId = null;
// Add or update entities
itemRepo.AddOrUpdate(oldItem);
itemRepo.AddOrUpdate(newItem);
personRepo.AddOrUpdate(person);
uof.Commit(); // only does a SaveChanges()
}
}
}
Solution 2 (update database AND property)
public partial class MyPage
{
private MyService service;
private Person person;
protected void Page_Load(object sender, EventArgs e)
{
service = new MyService();
person = service.GetCurrentPerson(Request.QueryString["id"]);
...
}
protected void SelectNewItem(object sender, EventArgs e)
{
Guid itemId = Guid.Parse(((Button)sender).Attributes["id"]);
service.SelectNewItem(person, itemId);
UpdatePage();
}
private void UpdatePage()
{
// Update controls values using person's properties here
}
}
public class MyService
{
private PersonRepo personRepo = new PersonRepo();
private ItemRepo itemRepo = new ItemRepo();
public void SwitchItems(Person person, Guid newItemId)
{
using (var uof = new UnitOfWork())
{
// Get the entities
Item newItem = itemRepo.Get(newItemId);
Item oldItem = person.Items.SingleOrDefault(i => i.Type == newItem.Type)
// Update the values
newItem.PersonId = person.Id;
oldItem.PersonId = null;
person.Items.Remove(oldItem);
person.Items.Add(newItem);
// Add or update entities
itemRepo.AddOrUpdate(oldItem);
itemRepo.AddOrUpdate(newItem);
personRepo.AddOrUpdate(person);
uof.Commit(); // only does a SaveChanges()
}
}
}
How about refreshing your context to make sure you have the latest db changes after the .SaveChanges() method. Pass in the entity to be refreshed an call Refresh on the context:
((IObjectContextAdapter)_dbContext).ObjectContext.Refresh(RefreshMode.StoreWins, entityPassed);
Or leave the Commit() method as is and use a more dynamic approach something like:
var changedEntities = (from item in context.ObjectStateManager.GetObjectStateEntries(
EntityState.Added
| EntityState.Deleted
| EntityState.Modified
| EntityState.Unchanged)
where item.EntityKey != null
select item.Entity);
context.Refresh(RefreshMode.StoreWins, changedEntities);
The RefreshMode.StoreWins simply indicates that the database (store) takes priority and will override client (in-memory) changes.
If the Refresh method does not work, you can consider the following:
public void RefreshEntity(T entity)
{
_dbContext.Entry<T>(entity).Reload();
}
Or if all else fails, keep it simple and Dispose of your DbContext once you're done with each transaction (In this case after SaveChanges() has been called). Then if you need to use results after a commit, treat it as a new transaction and, instantiating a fresh DbContext and load your necessary data again.
Use Transection for example.
It's working fine.
public class UnitOfWork : IUnitOfWork
{
public readonly DatabaseContext _context;
private readonly IDbTransaction _transaction;
private readonly ObjectContext _objectContext;
public UnitOfWork(DatabaseContext context)
{
_context = context as DatabaseContext ?? new DatabaseContext ();
this._objectContext = ((IObjectContextAdapter)this._context).ObjectContext;
if (this._objectContext.Connection.State != ConnectionState.Open)
{
this._objectContext.Connection.Open();
this._transaction = _objectContext.Connection.BeginTransaction();
}
}
public int Complete()
{
int result = 0;
try
{
result = _context.SaveChanges();
this._transaction.Commit();
}
catch (Exception ex)
{
Rollback();
}
return result;
}
private void Rollback()
{
this._transaction.Rollback();
foreach (var entry in this._context.ChangeTracker.Entries())
{
switch (entry.State)
{
case System.Data.Entity.EntityState.Modified:
entry.State = System.Data.Entity.EntityState.Unchanged;
break;
case System.Data.Entity.EntityState.Added:
entry.State = System.Data.Entity.EntityState.Detached;
break;
case System.Data.Entity.EntityState.Deleted:
entry.State = System.Data.Entity.EntityState.Unchanged;
break;
}
}
}
public void Dispose()
{
if (this._objectContext.Connection.State == ConnectionState.Open)
{
this._objectContext.Connection.Close();
}
_context.Dispose();
}
}

insert in other tables after calling savechanges()

I made a simple framework to Doing the CRUD using MVVM for my application as below (for the add method ):
public int Add<TEntity>(TEntity entity) where TEntity : class
{
if (entity == null)
{
throw new ArgumentNullException("entity");
}
string entityName = GetEntityName<TEntity>();
var fqen = GetEntityName<TEntity>();
object originalItem;
EntityKey key = ObjectContext.CreateEntityKey(fqen, entity);
if (ObjectContext.TryGetObjectByKey(key, out originalItem)) return 0;
ObjectContext.AddObject(entityName, entity);
int r = ObjectContext.SaveChanges();
return r;
}
//And the Context
private ObjectContext ObjectContext
{
get
{
return GlobalContext.MainObjectContext;
}
}
//and the Singltone
public static S_Entities MainObjectContext
{
get
{
return Singleton.Instance();
}
}
in the ViewModel the Save() is implemented as below:
public void SaveItem()
{
storeReceiptBusiness.Insert(CurrentItem);
Items.Add(CurrentItem);}
the problem is when is call the Save() ,The CurrentItem will insert as expected but the currentItem of the last added entity and the CurrentItem of the current ViewModel will added!
I need to Discard old changes and only add the Current value.
hope it was clear.
Thanks indeed.

Categories

Resources