Entity Framework, how to avoid this issue? - c#

I have a method like this:
public FbUser FindUserByGraphOrInsert(dynamic json, bool commit = false)
{
string graphId = json.id;
EntityDataModelContext context = DataContext.GetDataContext();
FbUser user = context.FbUsers.FirstOrDefault(u => u.FbGraphId == graphId);
if (user == null)
{
user = new FbUser();
user.FbGraphId = json.id;
user.FbUsername = StringExtensions.UnicodeDecode(json.name);
context.FbUsers.AddObject(user);
if (commit)
context.SaveChanges();
}
return user;
}
I call this method repeatedly in a loop (say upwards of 80 times), with commit = false
Thing is, I expected this method to let me know if the user is already in the context, but this doesn't seem to be the case.
The result is that when I finally save changes, I get a list of 80 users, where 27 are distinct.
I expect this method to return those 27, how could I change it to achieve this?
Do I really need to save changes every single time?

You cant 'simply' do that, the problem is that each query will always hit the database by default since EF has no way of knowing you either query the same data or that there have been no underlying changes in the database since you opened the connection.
You can however check the ChangeTracker/ObjectStateManager for existing changed objects and query that one as well prior to deciding to add a new object.
Sample:
var addedObjects = context.ObjectStateManager.GetObjectStateEntries(System.Data.EntityState.Added);
var equalObjects = addedObjects.OfType<MyEntity>().Where(x => x.Name == newObject.Name);

Based on Polity's answer, I implemented the following extension method, which worked.
public static IEnumerable<T> IncludeUnsaved<T>(this ObjectSet<T> set) where T : class
{
var addedObjects = set.Context.ObjectStateManager.GetObjectStateEntries(System.Data.EntityState.Added);
var equalObjects = addedObjects.Select(e => e.Entity).OfType<T>();
return equalObjects.Concat(set);
}

Related

How can I detect changes to properties in a specific entity in Entity Framework Core 2.x?

When a user saves changes to an object myObject, I'd like to log the fields that were updated to that object.
I can get an object with
var myObject = await context.MyObjects.SingleAsync(x => x.Id == id);
And I see that I can get an IEnumerable<PropertyEntry> with
var changes = context.Entry(myObject).Properties.Where(x => x.IsModified);
But in my changes list I don't see the field name anywhere. Also, it seems to take 2 full seconds to make this members query in LINQPad. That doesn't seem right.
How do I complete the following statement?
Consolse.Write($"The field {what goes here?} was updated from {change.OriginalValue} to {change.CurrentCalue}.");
Other StackOverflow questions I've found are for previous versions of Entity Framework, or override SaveChanges and don't look for specific entities.
Update! Got it.
public string GetChangeLog<T>(
ApplicationDbContext context,
T entity)
{
var sb = new StringBuilder();
var changes = context.Entry(entity).Properties.Where(x => x.IsModified);
foreach (var change in changes)
{
var propertyBase = (IPropertyBase)change.Metadata;
sb.Append($"\"{propertyBase.Name}\" was changed from \"{change.OriginalValue}\" to \"{change.CurrentValue}\"\n");
}
return sb.ToString();
}
Use the .Metadata property to retrieve a IPropertyBase. That will tell you what has actually changed.

Variable assignment to first item in IEnumerable does not work

I am attempting to assign the value true to a field in my collection of objects. I am using the First() method to retrieve the first object, and assign to it. In this example, I am assigning the value true to the Show variable. However, immediately after the assignment, it appears that Show variable is still false:
public class CallerItem
{
public int IndexId;
public string PhoneNumber;
public bool ToInd;
public bool Show;
}
public void myFunc() {
var callers = dbCallerRecs.Select(x => new CallerItem() { IndexId = x.IndexId, PhoneNumber = x.PhoneNumber, ToInd = x.ToInd });
var toCallers = callers.Where(x => x.ToInd);
if (toCallers.Any())
{
toCallers.First().Show = true;
Console.Log(toCallers.First().Show); //THIS LOGS 'false'. HOWEVER, IT SHOULD LOG 'true'
}
}
Is there something I am missing? Perhaps my understanding of the references returned from the Where clause is not right?
if (toCallers.Any())
{
toCallers.First().Show = true;
Console.Log(toCallers.First().Show); //THIS LOGS 'false'. HOWEVER, IT SHOULD LOG 'true'
}
Every time you call .First() you are getting the first item. For some enumerables (e.g. IQueryable) it will return a different object every time.
The below code will call the method only once and thus avoid the issue. Note also that I have used FirstOrDefault rather than Any then First - since the former will result in fewer DB queries (i.e. be faster).
var caller = toCallers.FirstOrDefault().
if (caller != null)
{
caller.Show = true;
Console.Log(caller.Show);
}
var callers = dbCallerRecs.Select(x => new CallerItem() { IndexId = x.IndexId, PhoneNumber = x.PhoneNumber, ToInd = x.ToInd });
var toCallers = callers.Where(x => x.ToInd);
defines a query which is evaluated when some elements in the resulting IEnumerable<CallerItem> (or IQueryable<CallerItem> which implements IEnumerable<CallerItem>) is iterated. This happens three times in your code - when calling Any and both times you call First (assuming .Any() returns true).
The reason you see this behaviour is the two calls to First cause the query to be re-evaluated and a new object to be created for each call, so you're modifying a different object the one you end up logging.
One solution would be to eagerly evaluate the query:
var toCallers = callers.Where(x => x.ToInd).ToList();

Update a list of entities with corresponding fields equal to an external list

I have an entity called Hazaa containing two fields: Killed (type DateTime?) and Linky (type Guid). The business logic dictates that whenever we create a new instance of Hazaa, the old one is supposed to be killed by setting the time stamp.
So, doing it for a single element, I apply the method as follows.
public void Create(Hazaa newbie)
{
using (ModelContainer context = new ModelContainer())
{
Hazaa oldie = context.Hazaas
.Single(hazaa => hazaa.Linky == hazaa.Linky && !hazaa.Killed.HasValue);
oldie.Killed = DateTime.Now;
context.Hazaas.AddOrUpdate(oldie);
context.Hazaas.Add(newbie);
context.SaveChanges();
}
}
Now, I'd like to use a similar approach for bulk update. The signature of that method would be following.
public void Create(List<Hazaa> newbies)
{
...
}
My problems is that I'm not sure how to perform the selection of pre-existing hazaas given the list of new additions. One way is to apply foreach statement and execute them one by one (but that's a slow method). Another one would be to use Contains method (but that's a problem because the list might long).
Are there any other options? My colleague suggested the following. I feel that it might be refactored so that it doesn't access context so many times. Do I worry without reason, perhaps?
public void Create(List<Hazaa> newbies)
{
...
using (ModelContainer context = new ModelContainer())
{
List<Hazaa> oldies = newbies.Select(hazaa => context.Hazaas
.Single(oldie => oldie.Linky == hazaa.Linky && !oldie.Killed.HasValue))
.ToList();
...
}
...
}
Since you use Single I assume that all Linkys represent existing records. This means that you can update them without even fetching them from the database first:
using (ModelContainer context = new ModelContainer())
{
foreach(var hazaa in newbies)
{
context.Attach(hazaa);
hazaa.Killed = DateTime.Now; // Will mark hazaa as modified
context.Add(new Hazaa { Linky = hazaa.Linky };
context.Hazaas.Add(newbie);
}
context.SaveChanges();
}

Running inline SQL with Entity Framework 5 and returning tracked Entities

How can I execute a command such as:
SELECT * FROM CATS
That behaves exactly as if I'd done myContext.Cats.Where(c => true); ?
Google suggested context.Database.ExecuteSqlCommand() but that returns an int. There is also context.Database.SqlQuery which looks promising, but it says that entities returned are not tracked, which I suspect is important (I'm not really familiar with how EF works in terms of tracking things).
It suggests using System.Data.Entity.DbSet<TEntity>.SqlQuery(Object[]) to track it, but I'm not entirely sure what this means or how to implement it.
In addition to make it even more confusing I want to write a generic method to allow me to execute the specific query against any table.
Here's a rough example of what I'd like in pseudocode
public DbSet<T> ExecuteSelect<T>(DbContext context, string table)
{
DbSet<T> entities = context.RunSql("SELECT * FROM " + table);
return entities;
}
Any ideas?
Accoring to this: http://msdn.microsoft.com/en-us/data/jj592907.aspx you want the following:
public IEnumerable<T> ExecuteSelect<T>(DbContext context, string table)
{
IEnumerable<T> entities = context.Set<T>.SqlQuery("SELECT * FROM " + table).ToList();
return entities;
}
myContext.Cats.Where(c => true) returns IQueriable<Cat> (not DbSet)
BUT
Your returned set will actually be finalized already (eg you cant add extra bits to your query later) so having it Queriable is misdirecting.
You can execute sql queries directly as follows :
private int DeleteData()
{
using (var ctx = new MyEntities(this.ConnectionString))
{
if (ctx != null)
{
//Delete command
return ctx.ExecuteStoreCommand("DELETE FROM ALARM WHERE AlarmID > 100");
}
}
return 0;
}
For select we may use
using (var context = new MyContext())
{
var blogs = context.MyTable.SqlQuery("SELECT * FROM dbo.MyTable").ToList();
}

Context.SaveChanges() in LINQ

This is an easy question and I want to confirm with you experts!
I am changing some values in two different tables in same database. Is it okay to just call SaveChanges one time to update all? Seems to work but I want to know if its okay or need to call context.SaveChanges after every update?
bool success = false;
TestSuiteDB context = new TestSuiteDB();
var workstyle1 = context.WorkStyle.Where(d => d.WorkStyleId==21 && d.MemberId==1).ToList();
foreach (var ws1 in workstyle1)
{
ws1.ModuleId = flid1;
}
var workstyle2 = context.WorkStyle.Where(d => d.WorkStyleId == 22 && d.MemberId==1).ToList();
foreach (var ws2 in workstyle2)
{
ws2.ModuleId = flid2;
}
var workstylemodules1 = context.WorkStyleModules.Where(d => d.WorkStyleModuleId == 2 && d.MemberId == 1).ToList();
foreach (var ws1 in workstylemodules1)
{
ws1.ModuleId = flid1;
}
var workstylemodules2 = context.WorkStyleModules.Where(d => d.WorkStyleModuleId == 3 && d.MemberId == 1).ToList();
foreach (var ws2 in workstylemodules2)
{
ws2.ModuleId = flid2;
}
if (context.SaveChanges() > 0)
{
success = true;
}
You can call Context.SaveChanges(); once after all the updates and it will save all the changes done previously.
If you are expecting to change/insert a new object, and wants to modify the next objects based on the database's actual value, you may call Context.SaveChanges() so that the database gets updated.
Any changes/insertion to the entities are tracked in the context and once SaveChanges is called, it put the changes in the database as well.
Also keep this in mind:
From MSDN:
SaveChanges operates within a transaction. SaveChanges will roll back
that transaction and throw an exception if any of the dirty
ObjectStateEntry objects cannot be persisted.
I think it's better to call it once.you may also need a transactional code if you want to get a block excution.
Technically it's perfectly legal to do it either way.
BUT, as Habib.OSU already said, there may be more things to consider, like dependencies between the entities you are going to save. There would several strategies to handle this.
AND, if you call SaveChanges() for each loop iteration this might cause heavy performance issues.

Categories