Write to a dynamic Column in EF Database - c#

I currently have many if statement when saving data to my database because the Column changes dynamically. My code would be far more efficient if I could use the "name" string to dynamically change the column that I need to save too.
if (String.Compare(name, "MWSAccessKeyID", StringComparison.Ordinal) == 0)
{
var DBobj = db.AccountsConfigDatas.Find(0);
DBobj.MWSAccessKeyID = NewValue;
db.Entry(DBobj).State = EntityState.Modified;
db.SaveChanges();
}
What I have tried:
I have researched this topic for a couple of hours. I believe I'm using the wrong search terms because there is not a lot out there. I tried finding the Column using the name string in a LINQ.
var Column = DBobj
.Where(e == name)
.Select(e)
.FirstOrDefault();
DBobj.Column = NewValue;
db.Entry(DBobj).State = EntityState.Modified;
db.SaveChanges();
Any suggestions or documentation from the community would be very helpful.

It seems that X-editable forces you to update properties by name. In my opinion your architecture shouldn't allow idiosyncrasies of a UI library to bleed into the deeper regions of your application code. An abstraction/mapping layer between controllers and DAL (or services) is recommended.
Anyway, as long as you don't have that, with Entity Framework it's easy to modify properties by name. No reflection required because EF has cached the meta data of the entity model:
var dbObj = db.AccountsConfigDatas.Find(id);
db.Entry(dbObj).CurrentValues[propertyName] = newValue;
db.SaveChanges();
This updates one property by name and marks this single property as modified, so only this property will be in the update statement. It's bad practice to set the entity's state as modified because that generates an update statement containing all of its properties.

var DBobj = db.AccountsConfigDatas.Find(0);
var propertyInfo = DBobj.GetType().GetProperty(name);
propertyInfo.SetValue(DBobj, NewValue);
db.Entry(DBobj).State = EntityState.Modified;
db.SaveChanges();
Documentation:Type.GetProperty()

Related

Better way to update/insert values using EF

I am trying to solve a problem as well as learn and improve my code skills here.
I am using Entity Framework and am tasked with writing to a SQL table, where I can update or insert based on whether a row exists or not. If it doesn't exist then add it, if it does exist then update it if required.
I have 2 lists, the first list is the EF type from the table that I am writing to. The second list is a class which is made up from a SQL query but shares some of the columns from the table which needs updating, thus if they differ then update the table with the differing property values.
foreach (var tbl in Table_List)
{
foreach (var query in SQL_Query)
{
if (tbl.ID == query.ID)
{
bool changed = false;
if (tbl.Prop1 != query.Prop1)
{
tbl.Prop1 = query.Prop1;
changed = true;
}
if (tbl.Prop2 != query.Prop2)
{
tbl.Prop2 = query.Prop2;
changed = true;
}
if (changed)
await Context.SaveChangesAsync();
}
}
}
There are 10 properties in total in the class, but even if all 10 of them differ I only have to update 2 properties, the rest can stay the same. So to summarize, my question is;
Is there a better way to update these 2 properties? Something other than a bulky series of if statements and foreach loops? Any info on straight up inserting would be appreciated too, thanks very much!
EF uses an internal ChangeTracker. This means that when you change a property of an entity that is being tracked (you queried the lists using a DbSet on the Context) it will marked as "Changed" in the ChangeTracker. This is used by the SaveChangesAsync to determine what to do, ie. insert of update and what fields need to be updated.
Second, this ChangeTracker is smart enough to detect that when you set a property to the same value it already has it won't be marked as a Change.
Also with this ChangeTracker there is no need to call SaveChangesAsync after every change. You can call it at the end of the loop.
foreach (var tbl in Table_List)
{
foreach (var query in SQL_Query)
{
if (tbl.ID == query.ID)
{
tbl.Prop1 = query.Prop1;
tbl.Prop2 = query.Prop2;
}
}
}
await Context.SaveChangesAsync();

MVC reading from a dynamic list of SQL tables

I have an application that is using MVC framework that currently reads from a model object of a SQL table and puts the results in a list format. The table has its own .cs class with all the necessary fields and the code filters the results based on other factors.
The problem I am running into, is that I need to find a way to add new tables to this list without making changes to the code itself. Ideally, I would like to add a list of tables I need to read from into the web.config and create a class for them in the project structure and the code will dynamically read from that.
Currently with one table the code looks like this:
var assets = _model.FMIF.ToList();
var results = BuildFormatedResult(assets);
And I have been able to add another table to this structure like this:
var assets = _model.FMIF.ToList();
var assets2 = _model.FMHZ.ToList();
assets = assets.Concat(assets2).ToList();
var results = BuildFormatedResult(assets);
I have tried to make this dynamic using a method like this:
var test = "FMIF";
var assets = _model.+test+.ToList();
var results = BuildFormatedResult(assets);
but it does not appear to be able to read the variable as a model.
Is there a best practice way to do something like this? Even if it is way different than what I have tried I am kind of at a loss here. Not super familiar with MVC structure so any help or advice is greatly appreciated.
Thanks!
This is an approach I've used to dynamically get a handle to a DbSet. Firstly I define a couple of methods (This does assume that you have the classes already generated for each of the tables.)
public static DbSet GetDbSet(MyDbContext db, string tableName)
{
// Find the EF entity that corresponds to this table
ObjectContext objectContext = (db as IObjectContextAdapter).ObjectContext;
var mappings = GetEntityMappings(objectContext);
var entityName = mappings[tableName];
// Now get the corresponding DbSet
DbSet dbSet = (DbSet)db.Set(Type.GetType("schema_name." + entityName));
return dbSet;
}
public static Dictionary<string, string> GetEntityMappings(ObjectContext objectContext)
{
var EntityMappings = new Dictionary<string, string>();
// Build a list of database table names to EF entity names
// Get a list of entities
MetadataWorkspace workspace = objectContext.MetadataWorkspace;
var entities = workspace.GetItems<EntityType>(DataSpace.CSpace);
foreach (EntityType et in entities)
{
// Get the entity set that uses this entity type
var entitySet = workspace
.GetItems<EntityContainer>(DataSpace.CSpace)
.Single()
.EntitySets
.Single(s => s.ElementType.Name == et.Name);
// Find the mapping between conceptual and storage model for this entity set
var mapping = workspace.GetItems<EntityContainerMapping>(DataSpace.CSSpace)
.Single()
.EntitySetMappings
.Single(s => s.EntitySet == entitySet);
// Find the storage entity set (table) that the entity is mapped to
var table = mapping
.EntityTypeMappings.Single()
.Fragments.Single()
.StoreEntitySet;
string tableName = (string)table.MetadataProperties["Table"].Value ?? table.Name;
EntityMappings.Add(tableName, et.Name);
}
return EntityMappings;
}
Basically it is poking around in the EntityFramework metadata in order to map a string of a table name to the entity name (which aren't necessarily the same). This is slightly simplified as ideally you would cache the mappings.
Then finally call the GetDbSet() method, I've used AsQueryable() as I potentially need to add where clauses etc.
var dbSet = GetDbSet(db, "table_name").AsQueryable();

Audit Entity Framework deleted entries preserving previous value

I've been struggling for a while with a problem that consists on auditing generically database entities when they're saved. I have a project that uses EF 6 and it was required to me to create a "non-invasive" method to audit entities when they're added, modified or deleted. I have to store a JSON of the inserted entity, modified entity or deleted entity without interfering with the normal flow. The project has a Database First implementation.
My solution was simple, add a partial class of any entity that the rest of the programmers want to audit implementing IAudit which is basically an empty interface to get all changes from entities that implement it.
public interface IAudit {}
I have a Currencies entity that just implement it without any other code (I could do something else in the future but I don't need it)
public partial class Currencies : IAudit
I override the SaveChanges method to look for entities to audit
public override int SaveChanges()
{
ChangeTracker.DetectChanges();
// This linq looks for new entities that were marked for audit
CreateAuditLog(System.Data.Entity.EntityState.Added);
CreateAuditLog(System.Data.Entity.EntityState.Modified);
CreateAuditLog(System.Data.Entity.EntityState.Deleted);
return base.SaveChanges();
}
The solution calls 3 times the CreateAuditLog because in the near future I need to implement a configuration to audit whatever the user decides, might be from a database configuration that is activated/deactivated by users.
Everything worked perfectly, I was able to get saved entities in the specified state:
private void CreateAuditLog(System.Data.Entity.EntityState state)
{
var auditedEntities = ChangeTracker.Entries<IAudit>()
.Where(p => p.State == state)
.Select(p => p.Entity);
... some code that do something else
foreach (var auditedEntity in auditedEntities)
{
... some information I required to add
strJSON = JsonConvert.SerializeObject(auditedEntity, new EFNavigationPropertyConverter());
... some code to save audit information
}
}
The problem is I lose every value in the Deleted state, I only get the ID, there's no information in the properties except the ID and there is no any possibility of extract it in any way. I looked for every single solution in StackOverflow and other websites and there is nothing to recover the original information.
How can I get the previous deleted values to store them in the same way I'm storing Added and Modified entities?
It took me a couple of days to figure it out. Might be the solution is a bit complex but I tried several less complex options with not a good result.
First, as I'm just auditing Delete in a different way I separated Deleted state from Added and Modified that work well with no change. Deleted state is a particular case and I treat it like that.
First, I needed to obtain the original values from the database. In the Deleted state they're gone, there's not any possibility of recovering them from the entity. It's possible to obtain them with the following code:
var databaseValues = this.Entry(auditedEntity).GetDatabaseValues();
The result is just a collection of DB property values (DbPropertyValues). If I can get the original values I set the original values from the deleted entity:
dbEntityEntry.OriginalValues.SetValues(databaseValues);
This line just fills the entity original values, it doesn't modify the current value at all. It's useful to do it that way because it takes some code to check every property and set it ourselves, it's an interesting shortcut.
Now, the problem is I don't have the entity to serialize, so I need a new one which in my case I create by reflection because I don't know the type (I receive entities that implement IAudit)
Type type = auditedEntity.GetType();
var auditDeletedEntity = Activator.CreateInstance(type);
This is the entity I will serialize to store the audit later.
Now, the complex part, I need to get the entity properties and fill them by reflection from the original values set in the entity:
foreach (var propertyInfo in type.GetProperties())
{
if (!propertyInfo.PropertyType.IsArray && !propertyInfo.PropertyType.IsGenericType)
{
var propertyValue = originalValues.GetValue<object>(propertyInfo.Name);
auditDeletedEntity.GetType().InvokeMember(propertyInfo.Name,
BindingFlags.Instance | BindingFlags.Public | BindingFlags.SetProperty,
Type.DefaultBinder, auditDeletedEntity, new[] { propertyValue });
}
}
I had to check generic and array types to avoid following EF relations that are not going to work with this method and I also don't need (I need the object not the whole tree)
After that I simply need to serialize the audited deleted entity:
strJSON = JsonConvert.SerializeObject(auditDeletedEntity, new EFNavigationPropertyConverter());
The code looks like this:
string strJSON = string.Empty;
if (state == System.Data.Entity.EntityState.Deleted)
{
var databaseValues = this.Entry(auditedEntity).GetDatabaseValues();
// Get original values from the database (the only option, in the delete method they're lost)
DbEntityEntry dbEntityEntry = this.Entry(auditedEntity);
if (databaseValues != null)
{
dbEntityEntry.OriginalValues.SetValues(databaseValues);
var originalValues = this.Entry(auditedEntity).OriginalValues;
Type type = auditedEntity.GetType();
var auditDeletedEntity = Activator.CreateInstance(type);
// Get properties by reflection
foreach (var propertyInfo in type.GetProperties())
{
if (!propertyInfo.PropertyType.IsArray && !propertyInfo.PropertyType.IsGenericType)
{
var propertyValue = originalValues.GetValue<object>(propertyInfo.Name);
auditDeletedEntity.GetType().InvokeMember(propertyInfo.Name,
BindingFlags.Instance | BindingFlags.Public | BindingFlags.SetProperty,
Type.DefaultBinder, auditDeletedEntity, new[] { propertyValue });
}
}
strJSON = JsonConvert.SerializeObject(auditDeletedEntity, new EFNavigationPropertyConverter());
}
}
else
{
strJSON = JsonConvert.SerializeObject(auditedEntity, new EFNavigationPropertyConverter());
}
Might be there's a better way but I seriously spent a good amount of time looking for options and I couldn't find anything better.
Any suggestion or optimization is appreciated.

How to update entities which are modified outside the DbContext?

I've a small problem with updating entities if the entity is changed outside the DbContext (is a detached entity). If I attach the modified entity, it's state is not modified.
My code looks like this:
var specificationToSave = GetSpecificationFromTmpStore(userSessionGuid);
using (var context = DataContextFactory.GetDataContext())
{
// this works for update, if I change the values inside the context while debugging
// but it breaks with new entities
context.Specifications.Attach(specificationToSave);
// this works for insert new entities, modified entities will be saved as new entities
context.Specifications.Add((specificationToSave);)
context.SaveChanges();
}
I know NHibernate and it's method SaveOrUpdate. NHibernate decides because of the values if it is updating or inserting the entities.
What is the best practice to do this with EF 4.x and with entities which are modified outside the DbContext?
How can I tell the EF that this entity is in modified state?
If you use the Attach approach on an entity which has already changed, you will also need to tell EF that the entity is modified, after attaching it.
context.Specifications.Attach(entity);
context.Entry(entity).State = EntityState.Modified;
context.SaveChanges();
An alternative is to fetch (with tracking), then update the fields, and save:
var entity = context.Specifications.First(s => s.Id == 1234);
entity.Name = "Foo";
... other changes here
context.SaveChanges();
Another option is to make the changes to the entity after you have reattached it, e.g. as per here
context.Specifications.Attach(entity);
entity.Name = "Foo";
... other changes here
context.SaveChanges();
Edit
You can use generics with DbSet - either class, or method - as follows:
public void Update<TEntity>(TEntity entity)
{
DbContext.Set<TEntity>().Attach(entity);
DbContext.Entry(entity).State = EntityState.Modified;
DbContext.SaveChanges();
}
Edit : For updating of detached Parent / Child Graphs
For updating of simple / shallow parent-child relationships where efficiency and performance is not important, simply deleting all old children and reinserting the new ones is an easy (although ugly) solution.
However, for a more efficient scenario requires us to traverse the graph, detect changes, and then add newly inserted, update existing, ignore unchanged, and delete removed items from the Context.
Slauma shows a great example of this here.
You might want to look at using GraphDiff, which can do all this leg work for you!
For disconnected entities, I found this solution.
For finding changes on an existing entity:
var existing = context.Find<Item>(1);
if (existing != null)
{
context.Entry(existing).CurrentValues.SetValues(changed);
}
Its EntityState will be Modified afterwards but only where there are actual changes.
Full example I did in a unit/integration test:
await using var context1 = new MyContext(new DbContextOptionsBuilder().UseSqlite("Data Source=demo.db").Options);
await context1.Database.EnsureDeletedAsync();
await context1.Database.EnsureCreatedAsync();
await context1.Items.AddAsync(new Item
{
Id = 1,
Name = "Something to start with"
});
await context1.SaveChangesAsync();
await using var context2 = new MyContext(new DbContextOptionsBuilder().UseSqlite("Data Source=demo.db").Options);
var existing = context2.Find<Item>(1);
var entry = context2.Entry(existing);
entry.CurrentValues.SetValues(new Item
{
Id = 1,
Name = "Something to start with"
});
entry.State.Should().Be(EntityState.Unchanged);
entry.CurrentValues.SetValues(new Item
{
Id = 1,
Name = "Updated now."
});
entry.State.Should().Be(EntityState.Modified);
Using Microsoft.EntityFrameworkCore.Sqlite and FluentAssertions.

ASP.NET MVC3 Code-First Error- attempting to update entity

I've checked this question and it seems to be related to what I need, but does not answer it exactly.
I have an entity (Sql Compact using EF Code First via MVC3- if that wasn't clear from the title) for an "Issue" (generic issue tracking, just for my own education understanding of how MVC3 works). The Issue class has a CreatedBy property (Int reference to a User who Created the Issue) and a CreatedDate property (DateTime). When I use the scaffolded code to update (modified only to prevent some updated date fields from being modified by the user):
if (ModelState.IsValid)
{
issue.LastActivity = (DateTime?)DateTime.Now.Date;
if (issue.ClosedBy != null) issue.ClosedDate = (DateTime?)DateTime.Now.Date;
startingIssue = null;
db.Entry(issue).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
I receive the error mentioned in the linked question (conversion of a datetime2 data type to a datetime data type etc., etc.,)
When I step through the code, it appears my CreatedBy and CreatedDate properties are not contained in the instance of issue that the controller is passing around. When I try to fix that by grabbing another copy of the issue from the db, and updating those to values:
var startingIssue = db.Issues.Find(issue.IssueId);
if (ModelState.IsValid)
{
if (issue.CreatedBy != startingIssue.CreatedBy) issue.CreatedBy = startingIssue.CreatedBy;
if (issue.CreatedDate != startingIssue.CreatedDate) issue.CreatedDate = startingIssue.CreatedDate;
issue.LastActivity = (DateTime?)DateTime.Now.Date;
if (issue.ClosedBy != null) issue.ClosedDate = (DateTime?)DateTime.Now.Date;
startingIssue = null;
db.Entry(issue).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
I get the concurrency violation: An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key.
So, how do I get EF to see the date which is already set in the DB (so it doesn't try to update the CreatedDate to 1/1/0001) without violating concurrency?
Edit
Okay... I found it. I was, apparently, looking for #Html.HiddenFor(model => model.[property]) and adding the editor to the view anyway. That seems a little silly and round-about to me, but it does work without having to add custom code to detach one object and substitute an updated one.
The short answer is that you've already loaded the entity into the context with the Find and you cannot later attach another one.
You are left with two options:
Detach the first instance, then attach the second
Copy the fields from the second instance to the first
I'll share code for the first option. First, add a Detach method to your DbContext implementation:
public void Detach(object entity)
{
var objectContext = ((IObjectContextAdapter)this).ObjectContext;
objectContext.Detach(entity);
}
Then call Detach instead of setting the variable to null
var startingIssue = db.Issues.Find(issue.IssueId);
if (ModelState.IsValid)
{
if (issue.CreatedBy != startingIssue.CreatedBy) issue.CreatedBy = startingIssue.CreatedBy;
if (issue.CreatedDate != startingIssue.CreatedDate) issue.CreatedDate = startingIssue.CreatedDate;
issue.LastActivity = (DateTime?)DateTime.Now.Date;
if (issue.ClosedBy != null) issue.ClosedDate = (DateTime?)DateTime.Now.Date;
// startingIssue = null;
db.Detach(startingIssue);
db.Entry(issue).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
If the CreateDate and CreatedBy fields are not in the edit form, the update action object will not have the db values.
The additional call to the db and resetting, as Ed's answer describes, can be avoided if you include those fields in the edit form. Then the normal model binding should pick them up and give them back to you on the update.

Categories

Resources