Entity Framework 4 Code First - Virtual properties not updating when changed to null - entity-framework

So I have a model in my domain similar to this:
public class Product
{
public virtual Tag Methodology { get; set; }
}
Then in an webform project I update it like so:
if (!string.IsNullOrWhiteSpace(ddlMethodology.SelectedValue))
product.Methodology = TagRepo.GetTagById(int.Parse(ddlMethodology.SelectedValue));
else
product.Methodology = null;
But this wasn't updating when product.Methodology was previously set to an object and I wanted to change it back to nothing. I.e. the product.Methodology = null; line didn't seem to work as expected.
When I ran it in the debugger I found that sometimes it would work and sometimes it wouldn't. After a small amount of hair pulling, I realised it was due to the proxy type that the entity framework was creating for that property at runtime and it was working when I inspected it in the debugger.
So to fix the issue, I created this hack which works well: (NOTE: now an else if)
if (!string.IsNullOrWhiteSpace(ddlMethodology.SelectedValue))
product.Methodology = TagRepo.GetTagById(int.Parse(ddlMethodology.SelectedValue));
else if (product.Methodology != null)
product.Methodology = null;
So I guess my questions are:
Am I doing something wrong?
Is there another way it can be done remembering to do a hack everytime?
Could it be considered a bug in the entity framework code first CTP?
Cheers,
Charles

Related

Why is my update in my plugin on my custom entity not occurring?

I'm writing an auto number plugin for MS Dynamics CRM 2015. It works on the creation of an opportunity, when a new number needs to be generated. The current number is stored in another entity, which is retrieved at the time of creating the opportunity and then adds 1. The auto number entity is then updated with the new number (except it isn't as this isn't working at the moment).
At the moment the number is retrieved and 1 is added to it and is used in the opportunity correctly. However, as the update to the auto number entity does not occur when another opportunity is created it gets the same number as the previous one.
Here's my plugin code so far:
protected void ExecuteGenerateOpportunityAutoNumber(LocalPluginContext localContext)
{
if (localContext == null)
{
throw new ArgumentNullException("localContext");
}
IPluginExecutionContext context = localContext.PluginExecutionContext;
IOrganizationService service = localContext.OrganizationService;
if (context.InputParameters.Contains("Target") && context.InputParameters["Target"] is Entity)
{
Entity entity = (Entity)context.InputParameters["Target"];
if (entity.LogicalName == OPPORTUNITY_ENTITY_NAME)
{
if (!entity.Attributes.Contains(OPPORTUNITY_REF_ID))
{
try
{
string newId = RetrieveAndUpdateLastId(service);
entity.Attributes.Add(OPPORTUNITY_REF_ID, newId);
}
catch (FaultException ex)
{
throw new InvalidPluginExecutionException("GenerateOpportunityAutoNumber plugin error: ", ex);
//tracingService.Trace("GenerateOpportunityAutoNumber plugin error: {0}", ex.Message);
}
}
}
}
}
The RetrieveAndUpdateLastId method code is below:
private string RetrieveAndUpdateLastId(IOrganizationService service)
{
lock (lastIdentifierLock)
{
string result = null;
ColumnSet cols = new ColumnSet();
cols.AddColumns(LAST_REF_LAST_VALUE, LAST_REF_PRIMARY_KEY);
QueryExpression query = new QueryExpression();
query.ColumnSet = cols;
query.EntityName = LAST_REF_ENTITY_NAME;
EntityCollection ec = service.RetrieveMultiple(query);
if (ec.Entities.Count >= 1)
{
foreach (Entity identifier in ec.Entities)
{
if (identifier.Attributes.Contains(LAST_REF_LAST_VALUE))
{
int? lastValue = identifier[LAST_REF_LAST_VALUE] as int?;
if (lastValue != null)
{
string newValue = (lastValue.Value + 1).ToString().PadLeft(7, '0');
result = String.Format("SN{0}", newValue); //This is clearly happening as I'm getting the next number back.
identifier[LAST_REF_LAST_VALUE] = lastValue.Value + 1;
//Tried this also:
//identifier.Attributes.Remove(LAST_REF_LAST_VALUE);
//identifier.Attributes.Add(LAST_REF_LAST_VALUE, lastValue.Value + 1);
service.Update(identifier); //This doesn't seem to be happening.
break;
}
}
}
}
return result;
}
}
No error is thrown but the update of the auto number just isn't happening. I've checked the user I'm running this as has the required update privileges on the auto number entity as well. Any ideas?
UPDATE
After debugging I found that it was throwing an error that the Principal user is missing the prvWrite privilege. This would explain why the update isn't happening, but now raises another issue. I've setup the plugin to run as a specific user (one with the correct privileges), but the Guid of the 'Principal user' in the error was of the calling user. Why would it run as the calling user when I've set it up to use a specific user?
UPDATE 2
I think I may have found the issue but wonder if anyone else can confirm / shed some more light on this. It seems that according to this, the issue may lie with the user not being in a specific AD group, specifically
User account (A) needs the privilege prvActOnBehalfOfAnotherUser,
which is included in the Delegate role.
Alternately, for Active Directory directory service deployments only,
user account (A) under which the impersonation code is to run can be
added to the PrivUserGroup group in Active Directory. This group is
created by Microsoft Dynamics CRM during installation and setup. User
account (A) does not have to be associated with a licensed Microsoft
Dynamics CRM user. However, the user who is being impersonated (B)
must be a licensed Microsoft Dynamics CRM user.
For my purposes I think the user I'm trying to run as needs to be in PrivUserGroup in AD (which it's not), otherwise it defaults to the calling user.
UPDATE 3
I've been able to identify 2 fundamental problems. The first is as explained above, in that the context always runs as the calling user. The 2nd is that when either giving the calling user system admin privileges OR creating the IOrganizationService with a null parameter it still doesn't update. HOWEVER, and this seems very odd, these 2 scenarios DO work when profiling the plugin. Why would this be?
UPDATE 4
It seems I may have resolved the issue, though I'm not certain (hence why I've not written an answer as yet). As per the documentation we've added the user to be impersonated into the PrivUserGroup. The plugin now works. However, I don't understand why this is needed. Also, is this best practice in this scenario or have I done something that should never be done?
On a related note I also unregistered the plugin before deploying it this time, so I'm now wondering if this solved this issue. To confirm I've now removed the user from the PrivUserGroup in AD, but this takes some time (not sure exactly how long) to filter through apparently. If it still works then it looks like this actually resolved it. Do you normally need to unregister a plugin before re-deploying it to make sure it works?
UPDATE 5
Ok, so this if my final update. I'm not marking this as the answer as I'm not 100% certain, but it appears that removing the assembly using the plugin registration tool may have done the trick. From everything I've read you shouldn't need to unregister a plugin to redeploy, so my perhaps my assembly was corrupt somehow and by removing it and creating it again using the new assembly solve the issue. Unfortunately I don't have the original assembly to test with.
I would suggest to debug your plugin. Following article contains a video that describes how to debug plugins using Plugin Debugger and Plugin Regitration Tool - http://blogs.msdn.com/b/devkeydet/archive/2015/02/17/debug-crm-online-plugins.aspx
Updated How to have 2 instances of IOrganizationService for user context and system:
Open Plugin.cs file.
Locate following code:
internal IOrganizationService OrganizationService
{
get;
private set;
}
Add following code after:
internal IOrganizationService SystemOrganizationService
{
get;
private set;
}
Find following code:
// Use the factory to generate the Organization Service.
this.OrganizationService = factory.CreateOrganizationService(this.PluginExecutionContext.UserId);
Add following code after:
this.SystemOrganizationService = factory.CreateOrganizationService(null);
Use this instance of IOrganizationService in the place where you need higher level of privileges.

How to properly Delete fields of an Entity?

I am having trouble when trying to delete a field of an Entity using Entity Framework (version 6.1.3).
Let's say I have two Entities: Person and Work.
I can change the work of a person without any issue, but when I try to express that the person is unemployed it does not work properly:
person.Work = null;
db.SaveChanges();
After running this code the person still will have the previous work, but if I use the debugger and check the Work property of person before running
person.Work = null;, everything will behave as expected.
Can someone please explain why reading the value first makes the code work properly and how to correctly delete the field?
var work = person.Work; \\ with this line here everything works as expected
person.Work = null;
db.SaveChanges();
Two things that are contributing to your issue:
Entity Framework determines what needs to updated during SaveChanges by tracking changes to property values.
You probably have lazy loading enabled (both in general and for the Work property), which means that if the person has an associated Work, that associated entity doesn't get loaded until the first time you access that property.
Putting those together, when you set person.Work = null without accessing person.Work (which would trigger a load), the context thinks nothing has changed. But if you load the property first, setting the property to null tells EF to remove that association. Edit: According to the page that octavioccl linked, this is true for .NET 4.0., but for .NET 4.5+ (and EF 5+), loading first is unneeded.
Possible solutions
If you want to remove the association without loading the related entity, you'll need to add a foreign key property to your Person entity, then you can set that to null instead of setting the navigation property to null. For example:
public class Person
{
// other properties...
public int? WorkId { get; set; }
public virtual Work { get; set; }
}
person.WorkId = null;
db.SaveChanges();
octavioccl's answer quoted another option:
context.Entry(person).Reference(p => p.Work).CurrentValue = null;
From this msdn page:
To delete the relationship, set the navigation property to null. If
you are working with the Entity Framework that is based on .NET 4.0,
then the related end needs to be loaded before you set it to null. For
example:
context.Entry(person).Reference(p => p.Work).Load();
person.Work = null;
Starting with the Entity Framework 5.0, that is based on .NET 4.5, you
can set the relationship to null without loading the related end. You
can also set the current value to null using the following method:
context.Entry(person).Reference(p => p.Work).CurrentValue = null;

How do I resolve "No views were found in assemblies or could be generated for Table"?

I am working with a database first model in Entity Framework 5 and when I attempt to add a row, I get the following error:
"No views were found in assemblies or could be generated for Table 'ui_renewals'."
The table exists in my EDMX and the template generated a ui_renewals class. I've deleted the table from the EDMX and added it again using the Update Model from Database option and I get the same error. Creating a separate connection for it resolves the issue, but that seems like a less-than-ideal solution (more like a kludge) not to mention it makes it more difficult to maintain in the future.
Any ideas on how to fix this so that I can add or update (I've tried both) a row in ui_renewals?
Here is the code I'm currently using - only difference before was using db as a DBContext instead of ui (yes, receipt is misspelled - gotta love legacy stuff)
[HttpPost]
public bool UpdateTeacher(string login_id, string password, UIRenewal data)
{
if (ModelState.IsValid)
{
// map from UIRenewal VM to ui_renewal
ui_renewals Renewal = Mapper.Map<UIRenewal, ui_renewals>(data);
// check to see if this is a new entry or not
var tmp = ui.ui_renewals.Find(Renewal.reciept);
if (tmp == null)
ui.ui_renewals.Add(Renewal);
else
{
// mark as modified
db.Entry(Renewal).State = EntityState.Modified;
}
// save it
try
{
ui.SaveChanges();
}
catch (DBConcurrencyException)
{
return false;
}
return true;
}
return false;
}
I should mention that I do have a view in the model (v_recent_license).
I know this is a very old question, however as I haven't found any other topics like this, I'll post my answer.
I have had the same Exception thrown. I found that, in a failed attempt to optimize EF performance, following the advices found here, I left behind this piece of code in EF .edmx code-behind:
<EntityContainerMapping StorageEntityContainer="XXXModelStoreContainer" CdmEntityContainer="YYYEntities" GenerateUpdateViews="false">
I removed the GenerateUpdateViews="false" string, and all is working again.
(The Exception message is a little misleading in my opinion).

EF Code first problem - related entity not loaded

this is really annoying
I have something like this:
class Person {
..properties id, name etc..
}
class Task {
..properties id, name etc..
Person Moderator {get;set}
}
public class DataModel : DbContext {
public DbSet<Task> Tasks { get; set; }
public DbSet<Person> People { get; set; }
}
I can then create new tasks and add People objects to the task and save, and I can see the data correctly saved in the sql backend - each Task saved has the correct Person id saved
with it and the Person with that id is saved back as well.
But when I try and get back a task, the person object is always null.
using (DataModel db = new DataModel()) {
Task t = db.Tasks.SingleOrDefault(p => p.Id == 22);
assert(t.Name.Lenght>0)
assert(t.Moderator != null) // always null!!!!!!
....
}
What do I have to do to get the whole object graph bought back? Do I have to do a join in the SingleorDefault call? seems a bit wrong somehow.
Did I mention this is really annoying.
TIA,
Two options for you. By default the code first / dbContext model returns a proxy object that derives from your model (this is important to understand when you run into JSON serialization issues). The proxy object uses lazy loading of associations but only under certain circumstances. The Moderator property has to be declared as virtual so that the proxy can override it and do the lazy loading for you.
However lazy loading can create a problem called Select N+1. If in most cases you only need the Task and not the Moderator, this won't be a problem. However if you frequently display a list of tasks and their associated moderators, you will effectively have to run an extra round trip to the database for every task in that list in addition to the 1 for the original list(e.g. for a list of 100 tasks you would do 101 queries to display the tasks and their moderators).
To get around this, EF provides the Include operator, this forces the relation to load. Use it as such
Task t = db.Tasks.Include(t=>t.Moderator).SingleOrDefault(p => p.Id ==
22);
Hope this helps.
You have lazy loading turned off for your Moderator property, so it will only be loaded if you explicitly do so using Load().
You can force EF to eagerly load your related Person entity by using the Include() method in your query like this:
Task t = db.Tasks.Include(x => x.Moderator).SingleOrDefault(p => p.Id == 22)
There is a pretty good overview in this article.

Preventing Validation in Entity Framework 4

I'm using Entity Framework 4 and a Dynamic Data site to expose a bare-bones admin interface to a few users. Working pretty well in general, but I have run into this one problem on a couple of fields on my model.
Several tables have some audit-related fields - CreatedBy, CreatedDate, ModifiedBy, and ModifiedDate. These fields are required in the database and the associated models are marking the properties as non-nullable (all as it should be). However I am handing setting the values for these fields in code - the field templates for the field types mark these specific fields as disabled on the page, and in the SavingChanges event I set these fields to the appropriate values. All works great when I'm updating an existing item.
The problem comes in when I try to create a new item. I want these fields to remain empty on the page and be auto-populated by my code when submitted, but the Field Templates set up RequiredFieldValidators for these fields and won't let me submit them without a value. Normally this would be great, except that I want to prevent EF from validating these fields at the point of page submission.
I realize that I could mark the fields as nullable in the database and that would resolve the issue - it would probably even be just fine from the data standpoint, but I'm not comfortable with doing so - for one thing it's not unlikely that some of the models these fields appear on will be bulk loaded, possibly by someone else, at a later date. I would rather still have the database enforce the non-nullability of these fields. In the field templates I've tried moving the built-in SetUpValidator() call for the RequiredFieldValidator not to run when these specific fields are being loaded, and I've also tried disabling the RequiredFieldValidators and forcing their IsValid property to true. None of these actions allows me to submit the page.
Is there a way to tell EF/Dynamic Data to skip the validation for some fields?
EDIT
As noted below, I also tried marking them nullable in the model and not in the database, which caused an error: Problem in mapping fragments...Non-nullable column...in table...is mapped to a nullable entity property.
EDIT #2
I have found a solution that works, but requires modifying the auto-generated designer file for the entity set, which is fragile at best. I would love to know a "righter" way to do it, but if nothing becomes apparent in the next couple of days I'll post my own answer.
So here are the edits I found I had to make. When allowing the tool to create the entities in the edmx Designer.cs file I get properties like these:
for a datetime on the server side
[EdmScalarPropertyAttribute(EntityKeyProperty=false, IsNullable=false)]
[DataMemberAttribute()]
public global::System.DateTime CreatedDate
{
get
{
return _CreatedDate;
}
set
{
OnCreatedDateChanging(value);
ReportPropertyChanging("CreatedDate");
_CreatedDate = StructuralObject.SetValidValue(value);
ReportPropertyChanged("CreatedDate");
OnCreatedDateChanged();
}
}
for a varchar
[EdmScalarPropertyAttribute(EntityKeyProperty=false, IsNullable=false)]
[DataMemberAttribute()]
public global::System.String CreatedBy
{
get
{
return _CreatedBy;
}
set
{
OnCreatedByChanging(value);
ReportPropertyChanging("CreatedBy");
_CreatedBy = StructuralObject.SetValidValue(value, false);
ReportPropertyChanged("CreatedBy");
OnCreatedByChanged();
}
}
To make it work without validation for a DateTime property setting the IsNullable parameter of the EdmScalarPropertyAttribute to true is sufficient to avoid the issue. For the String property you also have to change the 2nd parameter of the SetValidValue method call to "true."
All of this said, the only reason that I'm leaving this as it is is because I don't expect to have to regenerated the entities more than once or twice before we move to a different platform for this site. And in this case, merging the version in I have checked in to git with the version generated by the tool allows me to avoid most of the headaches,
Here is my meta information for a read-only auto generated date field. I don't get validation controls validating these fields. Hope this helps.
[ReadOnly(true)]
[DataType(DataType.Date)]
[Column(IsDbGenerated = true, UpdateCheck = UpdateCheck.Never, AutoSync = AutoSync.Never)]
[UIHint("DateTime")]
[Display(Name = "Modified", Order = 1000)]
[DisplayFormat(ApplyFormatInEditMode = true, DataFormatString = "{0:d}")]
public object DateModified { get; private set; }