How to enable Seperate Audits Table in Entity Framework - entity-framework

I have a Entity Framework based database with a few entities/models/table. For e.g. Documents Model, I am want to track all changes to each record in that table, in a seperate table called DocumentChanges Model/Table.
Could you please guide me on how to enable/tell EF to track/audit all changes to the table in a separate table?, not just a date time stamp, but save the full record for every change in a separate table.

The library Audit.EntityFramework can help you to do what you want.
You'll need to implement your own DataProvider to store the data formatted as you wish.
For example:
void StartUp()
{
//Setup to use your own provider to store the data
Audit.Core.Configuration.Setup()
.UseCustomProvider(new YourDataProvider());
//Setup to audit EF operations only for the table Documents
//(Your DbContext must inherit from AuditDbContext)
Audit.EntityFramework.Configuration.Setup()
.ForAnyContext(x => x.IncludeEntityObjects())
.UseOptIn()
.Include<Documents>();
}
class YourDataProvider : AuditDataProvider
{
public override object InsertEvent(AuditEvent auditEvent)
{
//Get some enviroment info:
var userName = auditEvent.Environment.UserName
//Get the complete log for the EF operation:
var efEvent = auditEvent.GetEntityFrameworkEvent();
foreach(var entry in efEvent.Entries)
{
// each entry is a modified entity (updated, deleted or inserted)
if (entry.Action == "Update")
{
//You can access the column values
var value = entry.ColumnValues["ID"];
//...or the columns changes
var changes = entry.Changes.Select(ch => ch.ColumnName + ": " +
ch.OriginalValue + " -> " + ch.NewValue);
}
//... insert into DocumentChanges table
}
return id;
}
}

Related

Entity Framework is too slow during mapping data up to 100k

I have min 100 000 data into a Job_Details table and I'm using Entity Framework to map the data.
This is the code:
public GetJobsResponse GetImportJobs()
{
GetJobsResponse getJobResponse = new GetJobsResponse();
List<JobBO> lstJobs = new List<JobBO>();
using (NSEXIM_V2Entities dbContext = new NSEXIM_V2Entities())
{
var lstJob = dbContext.Job_Details.ToList();
foreach (var dbJob in lstJob.Where(ie => ie.IMP_EXP == "I" && ie.Job_No != null))
{
JobBO job = MapBEJobforSearchObj(dbJob);
lstJobs.Add(job);
}
}
getJobResponse.Jobs = lstJobs;
return getJobResponse;
}
I found to this line is taking about 2-3 min to execute
var lstJob = dbContext.Job_Details.ToList();
How can i solve this issue?
To outline the performance issues with your example: (see inline comments)
public GetJobsResponse GetImportJobs()
{
GetJobsResponse getJobResponse = new GetJobsResponse();
List<JobBO> lstJobs = new List<JobBO>();
using (NSEXIM_V2Entities dbContext = new NSEXIM_V2Entities())
{
// Loads *ALL* entities into memory. This effectively takes all fields for all rows across from the database to your app server. (Even though you don't want it all)
var lstJob = dbContext.Job_Details.ToList();
// Filters from the data in memory.
foreach (var dbJob in lstJob.Where(ie => ie.IMP_EXP == "I" && ie.Job_No != null))
{
// Maps the entity to a DTO and adds it to the return collection.
JobBO job = MapBEJobforSearchObj(dbJob);
lstJobs.Add(job);
}
}
// Returns the DTOs.
getJobResponse.Jobs = lstJobs;
return getJobResponse;
}
First: pass your WHERE clause to EF to pass to the DB server rather than loading all entities into memory..
public GetJobsResponse GetImportJobs()
{
GetJobsResponse getJobResponse = new GetJobsResponse();
using (NSEXIM_V2Entities dbContext = new NSEXIM_V2Entities())
{
// Will pass the where expression to be DB server to be executed. Note: No .ToList() yet to leave this as IQueryable.
var jobs = dbContext.Job_Details..Where(ie => ie.IMP_EXP == "I" && ie.Job_No != null));
Next, use SELECT to load your DTOs. Typically these won't contain as much data as the main entity, and so long as you're working with IQueryable you can load related data as needed. Again this will be sent to the DB Server so you cannot use functions like "MapBEJobForSearchObj" here because the DB server does not know this function. You can SELECT a simple DTO object, or an anonymous type to pass to a dynamic mapper.
var dtos = jobs.Select(ie => new JobBO
{
JobId = ie.JobId,
// ... populate remaining DTO fields here.
}).ToList();
getJobResponse.Jobs = dtos;
return getJobResponse;
}
Moving the .ToList() to the end will materialize the data into your JobBO DTOs/ViewModels, pulling just enough data from the server to populate the desired rows and with the desired fields.
In cases where you may have a large amount of data, you should also consider supporting server-side pagination where you pass a page # and page size, then utilize a .Skip() + .Take() to load a single page of entries at a time.

Using Drag-Sort ListView with SQLite DB

I'm trying to create a simple to-do-list app with an SQLite DB and a Drag-Sort ListView.
Right now I am binding data from an SQLite database cursor into a ListView using a SimpleCursorAdapter and adding items with an EditText view as explained in this tutorial.
I have moved everything into one activity and I have swapped the plain ListView with a Drag-Sort ListView. My issue is connecting the DB and Drag-Sort ListView together. The DLSV demos use a predefined array to fill fill the DSLV.
Snippet form DSLV:
DragSortListView lv = (DragSortListView) getListView();
lv.setDropListener(onDrop);
lv.setRemoveListener(onRemove);
array = getResources().getStringArray(R.array.jazz_artist_names);
list = new ArrayList<String>(Arrays.asList(array));
adapter = new ArrayAdapter<String>(this, R.layout.list_item1, R.id.text1, list);
setListAdapter(adapter);
Snippet from my existing code:
mDbHelper = new NotesDbAdapter(this);
mDbHelper.open();
...
mDbHelper.createNote(noteName, "");
fillData();
private void fillData() {
// Get all of the notes from the database and create the item list
Cursor c = mDbHelper.fetchAllNotes();
startManagingCursor(c);
String[] from = new String[] { NotesDbAdapter.KEY_TITLE };
int[] to = new int[] { R.id.text1 };
// Now create an array adapter and set it to display using our row
SimpleCursorAdapter notes =
new SimpleCursorAdapter(this, R.layout.notes_row, c, from, to);
setListAdapter(notes);
}
Thank you and sorry if this doesn't make all that much sense.
Since you cannot reorder the items in a Cursor, you have to create a mapping between Cursor positions and ListView positions. This is done for you by the DragSortCursorAdapter class and subclasses so that the drag and drop reordering is maintained visually. If you need the new orders persisted (like to your database), then you must implement that logic yourself. The method getCursorPositions() will help you with this.
I have made use of Drag sort list view. Its amazing! but instead of using the dragsortcursoradapter i created my own trick. here it is.
What i have done is that whenever i swapped any item, i passed the new swapped list in an array to the database, deleted the table and recreated the new updated table. here is my code
here is the code snippet from my database handler
public void onUpdateToDoTable(ArrayList<Task> taskList) {
SQLiteDatabase db = this.getWritableDatabase();
db.execSQL("DROP TABLE IF EXISTS " + TABLE_TASKTODO);
String CREATE_TASK_TODO_TABLE = "CREATE TABLE IF NOT EXISTS "
+ TABLE_TASKTODO + "(" + SEQ_ID + " INTEGER PRIMARY KEY,"
+ TASK_NAME + " TEXT )";
db.execSQL(CREATE_TASK_TODO_TABLE);
for (int i = 0; i < taskList.size(); i++) {
ContentValues values = new ContentValues();
values.put(TASK_NAME, taskList.get(i).getTask());
db.insert(TABLE_TASKTODO, null, values);
}
db.close();
}
then on using drop listener
i called the above method..
private DragSortListView.DropListener onDrop = new DragSortListView.DropListener() {
#Override
public void drop(int from, int to) {
Task item = adapter.getItem(from);
adapter.notifyDataSetChanged();
adapter.remove(item);
adapter.insert(item, to);
db.onUpdateToDoTable(list);
Log.d("LIST", db.getAllToDoTasks().toString());
}
};
db is my object of database handler. whole source code is available at dragdroplistview. amazing example. this was a life saver for me. Ciao!
There is a github project available at https://github.com/jmmcreynolds/dslv-db-demo
This demo includes a working example of how to setup a DragSortListView that will save the changes that you make to the list (position, add/delete) to a database.
I have used it just now. Its perfect demo project available for using DSLV with SQLiteDB.
Thanks to github user jmmcreynolds.

entity framework update

i know how to do updates, inserts and deletes with the entityframework but in this case i don't know what to do.
In this case i have 3 tables: the table A the table B and the table AB which has 2 columns, one is the foreing key of the table A and one is the foreing key of the table B.
The entity framework shows only the tables A and B so how i can update only the content of the table AB?
I've tried to use the references in entity A and entity B but it gives me an exception saying that the entityset AB doesn't have the insert function and the delete function.
You try to make a
Public Virtual List<int> Ids
in your "A" and "B" Class to recover all the associations
For an insert, you would create a record for Table A, then add the Table B records to the item created that inserts into A. EF will handle the rest.
var tableA = new TableAtype { Description = "blah", etc.};
tableA.TableBtype.Add(new TableBtype { Property1 = "foo", Property2 = "bar"};
yourContext.AddToTableAtype(tableA);
yourContext.SaveChanges();
i'll be more specific using the code of my project as asked by TheGeekYouNeed
public void ModificaAbilitazioni(int IdGruppoAnagrafica, List<DefAbilitazioni> AbilitazioniList)
{
GruppiAnag gruppo = (from g in entities.GruppiAnags
where g.IdGruppoAnag == IdGruppoAnagrafica
select g).First();
List<DefAbilitazioni> tutteAbilitazioni = GetTutteAbilitazioni();
for (int i = 0; i < AbilitazioniList.Count; i++)
{
if (tutteAbilitazioni[i].GruppiAnags.Contains(gruppo))
{
tutteAbilitazioni[i].GruppiAnags.Remove(gruppo);
}
}
foreach (DefAbilitazioni abilitazione in AbilitazioniList)
{
for (int i = 0; i < tutteAbilitazioni.Count; i++)
{
if (tutteAbilitazioni[i].IdAbilitazione == abilitazione.IdAbilitazione)
{
tutteAbilitazioni[i].GruppiAnags.Add(gruppo);
}
}
}
entities.SaveChanges();
}
ok...here it is
this method should change the privilegies accounts.
First i recover the account using his id, than i recover all the privilegies and if in their reference they have the account recovered, then i remove it from the reference.
This way the account doesn't have any privilegies. Now in the privilegies that i've passed calling the method and in theri reference i put the account. (just a wipe and refill i'm just trying for now...)
i've also di the opposite, wiping the privilegies references in the account and refill them, but in both way won't work, in the first case it says that the third entity (AB) doesn't have the insert function
i've resolved the thing, the problem was that i was working with objects not attached to the db, i've tried with the attach like this
foreach (DefAbilitazioni abilitazione in abilitazioni)
{
entities.Attach(abilitazione);
gruppo.DefAbilitazionis.Add(abilitazione);
}
but it doesn't work it says that the entitykey is null, maybe if somebody gives me an example of using the attach i'll try to change my code that now is like this
public void ModificaAbilitazioni(int IdGruppoAnagrafica, List<DefAbilitazioni> AbilitazioniList)
{
GruppiAnag gruppo = (from g in entities.GruppiAnags
where g.IdGruppoAnag == IdGruppoAnagrafica
select g).First();
IEnumerable<int> idAbilitazioni = from id in AbilitazioniList
select id.IdAbilitazione;
List<DefAbilitazioni> abilitazioni = (from abilitazione in entities.DefAbilitazionis
where idAbilitazioni.Contains(abilitazione.IdAbilitazione)
select abilitazione).ToList();
gruppo.DefAbilitazionis.Clear();
foreach (DefAbilitazioni abilitazione in abilitazioni)
{
gruppo.DefAbilitazionis.Add(abilitazione);
}
entities.SaveChanges();
}

updating table in entity framework 4 /mvc 3!

could you help with this? I bet this isn't any tough one..but am new to EF and facing a weekend deadline. I want to update a table with values.. but the primary key is identity column. So my task is like this.. if it exists, update.. if it doesn't add to the
table.. this is my code..and am stuck in this else part..!
Table structure is like this
Primary Key table - System: SystemId, SystemName
Foreign Key table - SystemConfiguration: SystemConfigurationId, SystemId, SystemRAM, SystemHard-Disk
public void SaveSystemConfigurations(SystemConfiguration systemConfig)
{
var config = (from s in Context.SystemConfiguration
where s.SystemId == systemConfig.SystemId
select s).FirstOrDefault();
if (config == null)
{
Context.SystemConfigurations.AddObject(systemConfig);
Context.SaveChanges();
}
else
{
// EntityKey systemConfigKey= new EntityKey("systemConfig", "systemConfigId", config.SystemConfigurationId);
Context.SystemConfigurations.Attach(systemConfig);
Context.SaveChanges();
}
}
Try this:
public void SaveSystemConfigurations(SystemConfiguration systemConfig)
{
var config = (from s in Context.SystemConfiguration
where s.SystemId == systemConfig.SystemId
select s).FirstOrDefault();
if (config == null)
{
Context.SystemConfigurations.AddObject(systemConfig);
}
else
{
config.Attribute = value; // Do your update here
}
Context.SaveChanges();
}
Edit. It should be config not systemConfig.
The ApplyCurrentValues method will apply scalar attributes to an entity that matches the same key. My assumption is that you are modifying a real entity (an object that has a valid entity key).
This would work:
var eSet = config.EntityKey.EntitySetName;
Context.ApplyCurrentValues(eSet, systemConfig);
Context.SaveChanges();
public void SaveSystemConfigurations(SystemConfiguration systemConfig)
{
var context = new EntitiesModel();
//here is the name of the partial class created on the Context area of the edmx designer.cs
var config = (from s in context.SystemConfiguration
where s.SystemId == systemConfig.SystemId
select s).FirstOrDefault();
context.ApplyCurrentValues(config.EntityKey.EntitySetName, systemConfig);
// systemConfig comes from the view with values set by the user
// you dont have to manually need to map one value at the time
context.SaveChanges();
}

Entity Framework - Issue returning Relationship Entity

Ok, I must be working too hard because I can't get my head around what it takes to use the Entity Framework correctly.
Here is what I am trying to do:
I have two tables: HeaderTable and DetailTable. The DetailTable will have 1 to Many records for each row in HeaderTable. In my EDM I set up a Relationship between these two tables to reflect this.
Since there is now a relationship setup between these tables, I thought that by quering all the records in HeaderTable, I would be able to access the DetailTable collection created by the EDM (I can see the property when quering, but it's null).
Here is my query (this is a Silverlight app, so I am using the DomainContext on the client):
// myContext is instatiated with class scope
EntityQuery<Project> query = _myContext.GetHeadersQuery();
_myContext.Load<Project>(query);
Since these calls are asynchronous, I check the values after the callback has completed. When checking the value of _myContext.HeaderTable I have all the rows expected. However, the DetailsTable property within _myContext.HeaderTable is empty.
foreach (var h in _myContext.HeaderTable) // Has records
{
foreach (var d in h.DetailTable) // No records
{
string test = d.Description;
}
I'm assuming my query to return all HeaderTable objects needs to be modified to somehow return all the HeaderDetail collectoins for each HeaderTable row. I just don't understand how this non-logical modeling stuff works yet.
What am I doing wrong? Any help is greatly appriciated. If you need more information, just let me know. I will be happy to provide anything you need.
Thanks,
-Scott
What you're probably missing is the Include(), which I think is out of scope of the code you provided.
Check out this cool video; it explained everything about EDM and Linq-to-Entities to me:
http://msdn.microsoft.com/en-us/data/ff628210.aspx
In case you can't view video now, check out this piece of code I have based on those videos (sorry it's not in Silverlight, but it's the same basic idea, I hope).
The retrieval:
public List<Story> GetAllStories()
{
return context.Stories.Include("User").Include("StoryComments").Where(s => s.HostID == CurrentHost.ID).ToList();
}
Loading the the data:
private void LoadAllStories()
{
lvwStories.DataSource = TEContext.GetAllStories();
lvwStories.DataBind();
}
Using the data:
protected void lvwStories_ItemDataBound(object sender, ListViewItemEventArgs e)
{
if (e.Item.ItemType == ListViewItemType.DataItem)
{
Story story = e.Item.DataItem as Story;
// blah blah blah....
hlStory.Text = story.Title;
hlStory.NavigateUrl = "StoryView.aspx?id=" + story.ID;
lblStoryCommentCount.Text = "(" + story.StoryComments.Count.ToString() + " comment" + (story.StoryComments.Count > 1 ? "s" : "") + ")";
lblStoryBody.Text = story.Body;
lblStoryUser.Text = story.User.Username;
lblStoryDTS.Text = story.AddedDTS.ToShortTimeString();
}
}