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.
Related
I'm having an issue with Entity Framework, when I execute SaveChanges, the context has quite a few objects associated with it, some of the objects are updated and some are added, afterwards I want to use the Id's of all these items (the Id's for the added items are only assigned on insert in the database). After the save changes a list of all the objects is empty.
I've seen samples on the site where the object is updated after the save so I suspect it might be how I'm getting the list of objects in the first place
Here's my code:
// Lots of processing to create or update objects
using (var localContext = this.context)
{
var updatedObjects = localContext.ChangeTracker.Entries().Where(e => e.Entity is GenerationEvent && (e.State == EntityState.Modified || e.State == EntityState.Added));
var updatedEvents = updatedObjects.Select(e => (GenerationEvent)e.Entity);
// The list has 5 items in at this point
localContext.SaveChanges();
// This list is now empty
DoSomethingWithList(updatedEvents);
}
Thanks in advance for any help.
The variable updatedEvents is a Linq query. Linq queries aren't executed immediately. By the time it is executed in your code it won't find any updated object anymore. Putting .ToList() after the Linq query will execute it immediately.
var updatedEvents = updatedObjects.Select(e => (GenerationEvent)e.Entity).ToList();
first your "using" statement is odd.
it should be
using (var context = new MyContext()) //passing in optional connection string.
{
}
Then the way you access your entities seem odd or i have no clue what you are doing there...
var updatedObjects = localContext.ChangeTracker.Entries().Where(e => e.Entity is GenerationEvent && (e.State == EntityState.Modified || e.State == EntityState.Added));
var updatedEvents = updatedObjects.Select(e => (GenerationEvent)e.Entity);
Seems like you are asking the context for all items which are considered "Add" or "Updated"
Then you are accepting the changes to the context. eg SaveChanges().
I fully expect "updatedEvents" to be empty, after save-changes is called.
Change you stuff... to something like
using (var context = new MyContext()) //passing in optional connection string.
{
LIst<EntityType> listOfChangedEntities = //ToDo:either passed in or generated
context.EntityType.AddRandge(listOfChangedEntities);
context.SaveChanges();
//after SaveChanges has been done all the entities in the
//listOfChangedEntities will now have there id's
//for update, fetch the entities... change them and Update them
}
I suspect that you are trying to create some sort of generic code to handle any type of Entity without specifying its type. Your code is not suited for this as it is, if this is what you are trying to do, I would modify the question to ask what you are trying to achieve. But the above is the Normal way of getting the Id's of the entities which have been inserted.
The other examples you are passably talking about is where they use foreign keys and navigation properties to automatically associate related entities, but your code looks way off from that.
UPDATE
routine
public static DoWork()
{
var context = new MyContext();
List<GenerationEvent > internalEntityType = new List<GenerationEvent ();
foreach(var item in SomeList)
{
var newItemEntity = new GenerationEvent();
newItemEntity.Name = "Test";
context.GenerationEvent.Add(newItemEntity);
//add to internal list
internalEntityType.Add(newItemEntity )
}
context.SaveChanges();
var first_id = internalEntityType.FirstOrDefault().Id;
//first_id will not be 0 it will be the Id the database gave it.
}
From several other classes I want to call this command to get the next id number. If there is 10 records, I want the number 11 returned.
Table1.id = NextId("table1");
public class Test
{
private PrenDBContext db = new PrenDBContext();
public NextId(string table)
{
return MaxId = db.Raknare.Where(x => x.Column.Equals(table)).Max(x => x.ID) + 1;
}
}
If I put public static NextId I cant use db..
Create a new context every time NextId is called. Don't try to re-use the contexts between calls. Due to connection pooling (which you should ensure is enabled, if it's not) creating a new context is not particularly expensive. In fact holding onto a context when it's not needed can be far more resource intensive.
Additionally, be careful about race conditions here. If you're looking to figure out what the ID of a new item is, you should really avoid trying to solve this problem on your own. Just use a column type that allows the DB to assign its own unique value for each row. Currently you need to deal with the case where another record is created after you run this query but before you add the new record (if that is indeed what you're doing). This is very hard to manage offsite form the database itself.
You should make your PrenDBContext static as well:
public class Test
{
private static PrenDBContext db = new PrenDBContext();
public static NextId(string table)
{
return MaxId = db.Raknare.Where(x => x.Column.Equals(table)).Max(x => x.ID) + 1;
}
}
It is though more recommended to create a separate PrenDBContext each time you call a NextId method:
public class Test
{
public static NextId(string table)
{
var db = new PrenDBContext();
return MaxId = db.Raknare.Where(x => x.Column.Equals(table)).Max(x => x.ID) + 1;
}
}
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.
In my application, users are able to be assigned roles. In the setup page for this, I have the following code:
foreach (string userRole in roleArray)
{
OrganizationRole orgRole = signedUpOrg.Roles.FirstOrDefault(target => target.Name == userRole && target.OrganizationId == signedUpOrg.OrganizationId);
if (orgRole != null)
{
OrganizationUser_OrganizationRole existingUserRole = orgRole.OrganizationUser_OrganizationRole.FirstOrDefault(target => target.Organization_User.User.UserId == orgUser.User.UserId &&
target.OrganizationRole.Name == userRole &&
target.OrganizationRole.OrganizationId == signedUpOrg.OrganizationId);
if (existingUserRole == null || orgUser.User.UserId == 0) // new user role to new users or existing users with new roles
{
orgRole.OrganizationUser_OrganizationRole.Add(new OrganizationUser_OrganizationRole
{
Organization_User = orgUser,
OrganizationRole = orgRole
});
}
}
}
With this, we are able to cycle through the roles which are to be assigned and save them in the database. This works perfectly fine on creating the user and their roles, but on editing there is no change. The code seems to be hit at all the crucial points with the correct data (roles, etc) but there is no reflection in the database or on the front end.
This code is in a method called SaveUsers in a class called CommonUtilities and is called in an AdministrationController with the following code:
CommonUtilities.SaveUsers(viewModel);
Can anyone possibly think of any reasons as to why this would work correctly on creation but not while editing? Many thanks in advance and I will be more than willing ot clarifiy on any points.
A similar issue drove me to choose NHibernate over EF as it can update children and grandchildren collections (probably even deeper levels but I have not tried that). Adjusting ObjectState like the answers referred to by #JhonatasKleinkauff seemed the only answer in EF but was not satisfactory in our case. This seems to only happen when you disconnect from the ObjectContext between retrieval and saving the parent object.
We have a model named Model1 and it has two attributes. Model1.Time, Model1.Text and Model1.Value, of course it has also ID as the requirement.
We are receiving the values from a SOAP service and registering them to our database but the question is: We have to check if these values are available already or not. What is the best possible way to do it?
Basically we are using a linq query.
DataContext db = new DataContext();
var x = (from y in db.Models where y.Value != new.Value && y.Text != new.Text select y).toList();
if { (x.Count == null) db.Models.Add(new); }
else {
foreach (var y in x) {
if (y.Time < new.Time) {
y.Time = new.Time;
y.Value = new.Value;
y.Text = new.Text;
db.ObjectStateManager.ChangeObjectState(y, EntityState.Modified);
}
}
db.SaveChanges();
}
Do you think that; this is the only possible approach or is there any more proper solution?
If you don't have the ID available from the service then I think what you have is the best you can do. or at least the approximate best. Hard to give you a better solution without knowing more about your use case or what you are trying to save.
Does what you have work? Is there something more specific about the code you posted you need help with?