Entity Framework create update query unnecessary [closed] - c#

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 9 years ago.
Improve this question
I am trying to update record with LINQ to SQL but in some case value is same as original value then also Enitty framework create Update query unnecessary.
var objForupdate = context.temp.FirstOrDefault();
if(objForupdate != null)
{
objForupdate.Name = "VJ"; // Original Value also "VJ"
}
// Create update query for above.
context.SaveChanges();
Updated
Hey Steven Wood
Here I have scenario where my DB has 20 fields. Sometime some data is same as original data then also Entity framework create update query for that.
It is simple if data row is not in dirty state then no need to update it. But entity frame work create Update query for that also. Just use profile tool to check what kind of query executed on DB server after SaveChanges() method executed.
Solutions
Use following function to check entity object changed or not. If not then it will change it to EntityState.Unchanged from EntityState.Modified.
public static bool ChangeStateIfNotModified(this EntityObject entity, ObjectContext context)
{
if (entity.EntityState == EntityState.Modified)
{
ObjectStateEntry state = ontext.ObjectStateManager.GetObjectStateEntry(entity);
DbDataRecord orig = state.OriginalValues;
CurrentValueRecord curr = state.CurrentValues;
bool changed = false;
for (int i = 0; i < orig.FieldCount; ++i)
{
object origValue = orig.GetValue(i);
object curValue = curr.GetValue(i);
if (!origValue.Equals(curValue) && (!(origValue is byte[]) || !((byte[])origValue).SequenceEqual((byte[])curValue)))
{
changed = true;
break;
}
}
if (!changed)
{
state.ChangeState(EntityState.Unchanged);
}
return !changed;
}
return false;
}

If you're looking to not execute the update if the two values are the same, why not do something like:
if(objForUpdate.Name != orignalValue){
context.SaveChanges();
}
Make sure you dispose your context where appropriate. For instance, if this is in a MVC controller, I'd dispose your context in the controller's Dispose() method.

You should use String.Empty instead of '' and verify that the value is really the same or not while debugging.
EDIT: Are you sure it's exactly the same value?
If I take a look at the generated code for a property, it looks like this:
[EdmScalarPropertyAttribute(EntityKeyProperty=true, IsNullable=false)]
[DataMemberAttribute()]
public global::System.String id
{
get
{
return _id;
}
set
{
if (_id != value)
{
OnidChanging(value);
ReportPropertyChanging("id");
_id = StructuralObject.SetValidValue(value, false);
ReportPropertyChanged("id");
OnidChanged();
}
}
}
private global::System.String _id;
So the value are being compared. Verify the code generated and set a breakpoint to debug it. If the state is changed, then a query would occur. If it's not entering inside the if condition and the update query still occur, the problem is elsewhere.

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();

Entity Framework check current value before update

We have a repository for CRUD operations. Basically if we want to do any of the read/write operations we can do something like
using (MemberRepo mRepo = new MemberRepo())
{
mRepo.UpdateEntity(member);
mRepo.Save();
}
What I need to know however is if it is possible to know a certain column value in the database before we update it? For example if there is a field in the member table called Status and I want to know the current existing status of that member before I update it with some other value?
Thanks.
The following code will let you set the creation date of any entity that has this column/attribute. It can work as a trigger in some cases.
public override int SaveChanges()
{
foreach (var entry in ChangeTracker.Entries().Where(entry => entry.Entity.GetType().GetProperty("CreationDate") != null))
{
if (entry.State == EntityState.Added)
{
entry.Property("CreationDate").CurrentValue = DateTime.Now;
}
}
return base.SaveChanges();
}

copying one objects data to other? [closed]

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 8 years ago.
Improve this question
I need to update EF record, in method I have EF object and another new objct which I want to use to update data from. But I m not sure how to copy data from new object to existing one.
Help please.
Here is my code:
public int PostHomeLead(string _lead)
{
try
{
int result = 0;
Lead lead = new Lead();
lead = new JavaScriptSerializer().Deserialize<Lead>(_lead);
//check if lead exist with same session id, if so update it other wise add new.
Lead existingLead = new Lead();
existingLead = db2.HomeLoanCustRepo.GetByID(lead.Lead_id);
if (existingLead == null)
{
db2.HomeLoanCustRepo.Insert(lead);
db2.Save();
result = 1;
}
else
{
db2.HomeLoanCustRepo.Update(lead);
db2.Save();
result = 1;
}
return result;
}
catch(Exception ex)
{
throw ex;
}
}
Either map the properties manually:
existingLead.Foo = deserializedLead.Foo;
existingLead.Bar = deserializedLead.Bar;
existingLead.Baz = deserializedLead.Baz;
Or use a library that does this, like AutoMapper.
As for your comment, creating a deep copy is what you seem to be after. Note this allows for overposting or mass assignment when you don't verify which properties may be updated. You'll need to Attach() the cloned object when using cloning or mapping, as it will not be the same object as returned by GetByID(), so Entity Framework's change tracker won't recognize it.

ASP.NET MVC 4 with EF code tuning issue [closed]

Closed. This question is opinion-based. It is not currently accepting answers.
Want to improve this question? Update the question so it can be answered with facts and citations by editing this post.
Closed 9 years ago.
Improve this question
The following code snippet is from our ASP.NET MVC 4 application that builds a menu based on the user. We're also using EF 6
MenuBarController
public ActionResult GetMenuList()
{
using (spc_aspEntities db = new spc_aspEntities())
{
ProgramMenuDAL programMenuDAL = new ProgramMenuDAL(db);
List<ProgramMenuDTO> programMenuList = programMenuDAL.GetMenuListForUser("1");
return View(programMenuList);
}
}
ProgramMenuDAL
public class ProgramMenuDAL : BaseDAL
{
private const string TOP_MENU_TYPE = "Menu";
private const string SUB_MENU_TYPE = "Submenu";
public ProgramMenuDAL(spc_aspEntities dbContext)
: base(dbContext)
{
}
public List<ProgramMenuDTO> GetMenuListForUser(string userLoginId)
{
//Get user info first
var userInfo = _dbContext.USER_TB.First(x => x.USER_SABUN == userLoginId);
string userGroupId = userInfo.USER_LEVEL;
//Retrieve all top menu first
var topMenuList =
from program_tb in _dbContext.PROGRAM_TB
where program_tb.PROGRAM_GB == TOP_MENU_TYPE
orderby program_tb.PROGRAM_ORDER
select new { program_tb.PROGRAM_ID, program_tb.PROGRAM_NAME };
Debug.Assert(topMenuList.Any(), "Top Menu is Empty");
List<ProgramMenuDTO> programMenuList = new List<ProgramMenuDTO>();
//Retrieve all sub menus
foreach (var topMenu in topMenuList)
{
var subMenuList =
from program_tb in _dbContext.PROGRAM_TB
from group_auth_tb in _dbContext.GROUP_AUTH_TB
where program_tb.PROGRAM_ID == group_auth_tb.PROGRAM_ID
&& program_tb.PROGRAM_SYSTEM == topMenu.PROGRAM_NAME
&& program_tb.PROGRAM_GB == SUB_MENU_TYPE
&& group_auth_tb.GROUP_ID == userGroupId
&& group_auth_tb.OPEN_YN == "True"
orderby program_tb.PROGRAM_ORDER
select new { program_tb.PROGRAM_ID, program_tb.PROGRAM_NAME };
if (!subMenuList.Any())
continue;
List<ProgramSubMenuDTO> programSubMenuList = new List<ProgramSubMenuDTO>();
subMenuList.ToList().ForEach(x=>programSubMenuList.Add(new ProgramSubMenuDTO(x.PROGRAM_ID, x.PROGRAM_NAME)));
programMenuList.Add(new ProgramMenuDTO(topMenu.PROGRAM_ID,topMenu.PROGRAM_NAME,programSubMenuList));
}
return programMenuList;
}
}
BaseDAL
public abstract class BaseDAL
{
protected readonly spc_aspEntities _dbContext;
protected BaseDAL(spc_aspEntities dbContext)
{
if (dbContext == null)
throw new ArgumentNullException("Database Context cannot be null");
_dbContext = dbContext;
}
}
The view corresponding to the controller is then loaded onto our shared layout using RenderAction.
#{Html.RenderAction("GetMenuList", "Menubar");}
Here are my question:
Does the code look sound architecturally? I've tried to separate business logic and data access layer (We've decided on not using the repository pattern). You can see that the query looks quite awkward, but given our initial database design, it seems like there's really no way around it.
This code is slowing down our website quite a bit. Before, each request to the main index would take about 50ms. Now it takes 500ms in debug mode, 200ms in release mode. What are some strategies I can use to tune the code?
--Edit--
One more question
We're wrapping all dbcontexts in using statement, requiring us to write the same code in every action. Would it be fine to just declare a private variable for the database and use it in the same class? What are some strategies around this?
I cringe when I see DAL and DTO combined with Entity Framework. EF is your DAL and provides DTO classes you can extend. You generally project the EF classes into Models in MVC.
Personally I would extend your DbContext with extra methods for specific tasks, and also avoid doing a query inside a for-each loop.
public partial class spc_aspEntities
{
public List<Models.ProgramMenu> GetMenuForUser(string userLoginId)
{
// existing code to get USER_LEVEL..
// create a single query to get your full menu structure
// your EF model should already include the relationship
// between PROGRAM_TB and GROUP_AUTH_TB
var query = this.PROGRAM_TB
.Where(p => p.PROGRAM_GB == TOP_MENU_TYPE)
.OrderBy(p => p.PROGRAM_ORDER)
.Select(p => new {
p.PROGRAM_ID,
p.PROGRAM_NAME,
// assuming there's also a relationship defined
// between PROGRAM_TB and itself on
// PROGRAM_SYSTEM == (parent).PROGRAM_NAME
SubMenus = p.ChildPrograms
.Where(cp => cp.PROGRAM_GB == SUB_MENU_TYPE)
.Where(cp => cp.GroupAuths
.Any(g => g.GROUP_ID == userGroupId
&& g.OPEN_YN == "True"
)
)
.Select(cp => new { cp.PROGRAM_ID, cp.PROGRAM_NAME })
});
// now project this into your model, ToList() forces the query to run so
// we can then perform non-sql manipulation (like newing up objects etc)
var programMenuList = query.ToList()
.Select(anon => new Models.ProgramMenu(
anon.PROGRAM_ID,
anon.PROGRAM_NAME,
anon.SubMenus.Select(sub => new Models.ProgramSubMenu(
sub.PROGRAM_ID,
sub.PROGRAM_NAME
).ToList()
)).ToList();
return programMenuList;
}
}
All this is created without any testing or deeper knowledge of your system. I see many issues with names and structure here, I've left most as-is but I would seriously consider code-readability as an important feature.
In my opinion you should add cache, because I am sure that menu doesn't change for user.
The second problem is that you don't get whole menu in one query instead you run query for each submenu.

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