How can you get foreign key values from a DBEntityEntry? - c#

I'm trying to create an audit log of any changes at point of save using Entity Framework. So far I have it working fairly well, storing all changes made to each field using the code below:
foreach (string propertyName in dbEntry.OriginalValues.PropertyNames)
{
// For updates, we only want to capture the columns that actually changed
if (!object.Equals(dbEntry.OriginalValues.GetValue<object>(propertyName), dbEntry.CurrentValues.GetValue<object>(propertyName)))
{
result.Add(new AuditLog()
{
UserID = UserId,
EventDateUTC = changeTime,
EventType = "M", // Modified
TableName = tableName,
RecordID = primaryKey.ToString(),
ColumnName = propertyName,
OriginalValue = dbEntry.OriginalValues.GetValue<object>(propertyName) == null ? null : dbEntry.OriginalValues.GetValue<object>(propertyName).ToString(),
NewValue = dbEntry.CurrentValues.GetValue<object>(propertyName) == null ? null : dbEntry.CurrentValues.GetValue<object>(propertyName).ToString()
});
}
}
The issue I'm facing is how to get values for any foreign keys that belong to this object. For example: I have a vehicle object that has relationships to a series of lookup tables, such as gearbox, model etc. If these values change the audit table will store the changed id, but I want to store the actual value.
Is there a way of getting the foreign key value in this situation?

Alrighty... this is an old question but I've spent the last while working this out because I had the exact same requirements. Maybe there is an easier way, but here's the code I used:
Your original code, slightly modified for my purposes (RecordID is always an int), and calling the new method to calculate the new value
foreach (string propertyName in dbEntry.OriginalValues.PropertyNames)
{
// For updates, we only want to capture the columns that actually changed
if (!Equals(dbEntry.OriginalValues.GetValue<object>(propertyName), dbEntry.CurrentValues.GetValue<object>(propertyName)))
{
var newVal = getNewValueAsString(dbEntry, tableName, propertyName);
result.Add(new AuditLog
{
UserID = currentUser.ID,
Timestamp = changeTime,
EventType = EventType.Modified,
TableName = tableName,
RecordID = dbEntry.OriginalValues.GetValue<int>(keyName),
ColumnName = propertyName,
OriginalValue = dbEntry.OriginalValues.GetValue<object>(propertyName) == null ? null : dbEntry.OriginalValues.GetValue<object>(propertyName).ToString(),
NewValue = newVal
}
);
}
}
A new attribute called "IsName"
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public class IsNameAttribute : Attribute
{
}
Marking the "name" property of foreign key models with the IsName attribute (note the code will default to a property called "Name" if it doesn't find one)
[Required]
[IsName]
public string Name { get; set; }
And the heavy lifting code
private string getNewValueAsString(DbEntityEntry dbEntry, string tableName, string propertyName)
{
var fkVal = getForeignKeyValue(tableName, propertyName, dbEntry.CurrentValues.GetValue<object>(propertyName));
return fkVal != null ? fkVal.ToString()
: (dbEntry.CurrentValues.GetValue<object>(propertyName) == null ? null
: dbEntry.CurrentValues.GetValue<object>(propertyName).ToString());
}
private object getForeignKeyValue(string tableName, string propertyName, object foreignKeyID)
{
// if this property is part of a foreign key, we need to instead look that up and store the value of the
// foreign key
// first get all the foreign keys in the system
var workspace = ((IObjectContextAdapter)this).ObjectContext.MetadataWorkspace;
var items = workspace.GetItems<AssociationType>(DataSpace.CSpace);
if (items == null) return null;
var fk = items.Where(a => a.IsForeignKey).ToList();
// now we look into the FK attributes and find that the "To Role" is out current table, and the
// "To Property" is out current property. The underscore is a bit of an assumption that the foreign
// key name built by EF will be ENTITY_BLAH_BLAH
var thisFk = fk.Where(x => x.ReferentialConstraints[0].ToRole.Name.StartsWith(tableName + "_"))
.FirstOrDefault(x => x.ReferentialConstraints[0].ToProperties[0].Name == propertyName);
// if fkname has no results, this is not a foreign key and we are done
if (thisFk == null) return null;
// Now that we know the foriegn key, we need to lookup the Name value in the other table
// find the assembly
var assembly = Assembly.GetCallingAssembly();
// build the type for the foreign key entity
// e.g. if the current entity is Task, and the property is StatusID, we are
// getting the "TaskStatus" type with reflection
// "User" class is an object in the Models namespace - you could just hardcode the string if you want
var foreignKeyType = assembly.GetType(typeof(User).Namespace + "." +
thisFk.ReferentialConstraints[0].FromRole.GetEntityType().Name);
// get the DbSet, same as: "(new DBContext()).EntityName"
var fkSet = Set(foreignKeyType);
// and find the row in that table
var fkItem = fkSet.Find(foreignKeyID);
// find the first column marked with the "IsName" attribute, otherwise default to "Name"
var nameColProperty = foreignKeyType.GetProperties()
.FirstOrDefault(p => p.GetCustomAttributes(typeof(IsNameAttribute), false).Any());
string nameCol = "Name";
if (nameColProperty != null) nameCol = nameColProperty.Name;
var nameColProperty2 = fkItem.GetType().GetProperty(nameCol);
if (nameColProperty2 == null) return null;
// get the value
var fkValue = nameColProperty2.GetValue(fkItem, null);
// and now, my brain hurts
return fkValue;
}

This solution is based on #JamesR's answer.
My goal was to make the code more generic so it could be used for multiple foreign keys connecting to different tables.
Improvements worth noting:
I moved the code that gets the list of foreign keys outside of the propertyName foreach loop. Since the list of FKs doesn't change based on the specific property, there is no reason to retrieve a new list every time. If there are many FKs in the system, this can take a while, so you don't want to repeat the process unnecessarily.
Instead of hard-coding a specific class type like GetType(typeof(User), I retrieved the foreign key table name from the FK using:
string lookUpTableName = thisFk.ReferentialConstraints[0].FromRole.Name;
Then, although the referenced FK property name will usually be ID, since it can vary, I retrieved the FK property name as well:
string lookUpPropertyName = thisFk.ReferentialConstraints[0].FromProperties[0].Name;
I then used ObjectContext.ExecuteStoreQuery to dynamically plug in the table and column name and retrieve the foreign key text value.
If a property is a FK, I get the FK text value for both the original a new value.
Complete code:
First, get a list of all the foreign keys in the system.
IObjectContextAdapter contextAdapter = ((IObjectContextAdapter)this);
MetadataWorkspace workspace = contextAdapter.ObjectContext.MetadataWorkspace;
var items = workspace.GetItems<AssociationType>(DataSpace.CSpace);
List<AssociationType> FKList = items == null ? null
: items.Where(a => a.IsForeignKey).ToList();
Then, loop through the list of properties and replace the original and current values with the foreign key values when a FK exists.
foreach (string propertyName in entry.OriginalValues.PropertyNames)
{
var original = entry.OriginalValues.GetValue<object>(propertyName);
var current = entry.CurrentValues.GetValue<object>(propertyName);
if (FKList != null)
{
GetPossibleForeignKeyValues(tableName, propertyName, ref original, ref current,
FKList, contextAdapter);
}
if ((original == null && current != null) ||
(original != null && !original.Equals(current)))
{
result.Add(new AuditLog()
{
UserID = UserId,
EventDateUTC = changeTime,
EventType = "M", // Modified
TableName = tableName,
RecordID = primaryKey.ToString(),
ColumnName = propertyName,
OriginalValue = original != null ? original.ToString() : "NULL",
NewValue = current != null ? current.ToString() : "NULL"
});
}
}
Here is the actually foreign key finding code:
private void GetPossibleForeignKeyValues(string tableName, string propertyName,
ref object originalFKValue, ref object newFKValue,
List<AssociationType> FKList, IObjectContextAdapter contextAdapter)
{
// If this property is part of a foreign key, look up and set the FKValue to the text
// value of the foreign key. Otherwise, just leave the FKValue alone.
// Look into the FK attributes and find that the "To Role" is out current table,
// and the "To Property" is out current property.
AssociationType thisFk = FKList.FirstOrDefault(x =>
tableName.Contains(x.ReferentialConstraints[0].ToRole.Name)
&& propertyName.Contains(x.ReferentialConstraints[0].ToProperties[0].Name));
// If fkname has no results, this is not a foreign key and we are done.
if (thisFk != null)
{
// Now that we know the foriegn key, look up the Name value in the other table.
string lookUpTableName = thisFk.ReferentialConstraints[0].FromRole.Name;
string lookUpPropertyName = thisFk.ReferentialConstraints[0].FromProperties[0].Name;
//Assuming the FK column name is "Name".
//Use the idea in #JamesR's solution or some sort of LookUp table if it is not.
string commandText = BuildCommandText("Name", lookUpTableName, lookUpPropertyName);
originalFKValue = contextAdapter.ObjectContext
.ExecuteStoreQuery<string>(commandText, new SqlParameter("FKID", originalFKValue))
.FirstOrDefault() ?? originalFKValue;
newFKValue = contextAdapter.ObjectContext
.ExecuteStoreQuery<string>(commandText, new SqlParameter("FKID", newFKValue))
.FirstOrDefault() ?? originalFKValue;
}
}
This is the method I used to build the SQL CommandText:
private string BuildCommandText(string columnName, string lookUpTableName,
string lookUpPropertyName)
{
StringBuilder builder = new StringBuilder();
builder.Append("SELECT ");
builder.Append(columnName);
builder.Append(" FROM ");
builder.Append(lookUpTableName);
builder.Append(" WHERE ");
builder.Append(lookUpPropertyName);
builder.Append(" = #FKID");
//The result query will look something like:
//SELECT ColumnName FROM TableName WHERE PropertyName = #FKID
return builder.ToString();
}

Related

Can't INSERT new Record due to Foreign Key Issue

I've seen a lot of posts about this particular error message but none seem to cover my problem. The error is :'The INSERT statement conflicted with the FOREIGN KEY constraint'
I have set a foreign key in the table called Tests as Null as not every record will have a corresponding record in the other table called APIS.
In SSMS I can insert a new record without any issue below is my INSERT query for doing so.
INSERT INTO [dbo].[Tests]
([APIID]
,[ActiveID]
,[ABCDataID]
,[ReferenceNumber]
,[LocationAddress]
,[LocationPostCode]
,[Make]
,[Model]
,[Registration]
,[WPC]
,[IsA]
,[IsS]
,[SelfSetDate]
,[IsBA]
,[UserID]
,[ClaimAtFaultEnum]
,[ClaimADriverEnum]
,[XRepair]
,[ManRepair]
,[HybridRepair]
,[BodyType]
,[CustomerName]
,[CustomerEmail]
,[CustomerMobileNumber]
,[IsCancelled]
,[CancellationNote])
VALUES
(NULL,
NULL,
NUll,
'111111111',
'Waterside',
'KA18 8EX',
'Abarth',
'320',
'TIL1607',
NULL,
1,
0,
NULL,
0,
NUll,
1,
1,
0,
0,
0,
'Car',
'John Smith',
'John#TIL1607TestData.com',
'07test',
0,
Null)
GO
Below is the same data I'm trying to put into the database using c#, it only seems to work when I set a foreign key. I can't put NULL as it's an int field and won't accept it and if I leave it out completely it comes up with the error message mentioned above.
Test testToAllocate = new Test();
if (testToAllocate != null)
{
int intClaimDriver = -1;
int intClaimAtFault = -1;
if (rdoDriverDriver.Checked)
{
intClaimDriver = (int)Enums.Test.Driver;
}
if (rdoNonDriver.Checked)
{
intClaimDriver = (int)Enums.Test.NonDriver;
}
if (rdoFaultFault.Checked)
{
intClaimAtFault = (int)Enums.Test.Fault;
}
if (rdoFaultNonFault.Checked)
{
intClaimAtFault = (int)Enums.Test.NonFault;
}
if (rdoFaultThirdParty.Checked)
{
intClaimAtFault = (int)Enums.Test.ThirdParty;
}
ABCData testToABC = db.AudaBridgeClaimDatas.Where(a => a.Registration == txtVehicleRegistration.Text).FirstOrDefault();
if (testToAllocate != null)
{
testToAllocate.ABCDataID = testToABC.ABCDataID;
}
else
{
testToAllocate.ABCDataID = null;
}
// testToAllocate.APIID = 5; //Needs to be Null
testToAllocate.ReferenceNumber = "111111111";
testToAllocate.LocationAddress = "Waterside";
testToAllocate.LocationPostCode = "KA18 8EX";
testToAllocate.Make = "Abarth";
testToAllocate.Model = "320";
testToAllocate.Registration = "TIL1607";
testToAllocate.IsA = true;
testToAllocate.IsS = false;
testToAllocate.IsBA = false;
testToAllocate.ClaimADriverEnum = 1;
testToAllocate.ClaimAtFaultEnum = 1;
testToAllocate.XRepair = false;
testToAllocate.ManRepair = false;
testToAllocate.HybridRepair = false;
testToAllocate.BodyType = "Car";
testToAllocate.CustomerName = "John Smith";
testToAllocate.CustomerEmail = "John#TIL1607TestData.com";
testToAllocate.CustomerMobileNumber = "07test";
testToAllocate.IsCancelled = false;
db.Claims.InsertOnSubmit(testToAllocate);
db.SubmitChanges();
Anyone got any ideas? It's almost as if visual studio hasn't recognized the change I made to the database to make this field null.
I can't put NULL as it's an int field and won't accept it
Then your model doesn't reflect your database. If your database has a nullable field, you need a nullable int: int?
if I leave it out completely it comes up with the error message mentioned above
Because the model has the wrong data type for that field, and the default value for int is 0. If there's no corresponding record 0 in the target table, that's a foreign key constraint violation.
It's almost as if visual studio hasn't recognized the change I made to the database to make this field null.
Indeed. If you're using some tool to generate your classes from the database, re-run that tool. If not, update your model accordingly. You have a nullable database field and a non-nullable model field.
Basically, where on your model you have something like this:
public int SomeField { get; set; }
What you want is something like this:
publit int? SomeField { get; set; }
Then you can set SomeField to null.
I have set a foreign key in the table called Tests as Null
I can't put NULL as it's an int field and won't accept it
Instead of int, make the type int? ,which is a nullable int. This will allow you set the value to null.
See MSDN

uCommerce Order Properties Missing

I have the following method to save order properties on a purchase order:
public void SetOrderProperty(string orderPropertyName, string value)
{
PurchaseOrder purchaseOrder = TransactionLibrary.GetBasket().PurchaseOrder;
OrderProperty orderProperty = purchaseOrder.OrderProperties.FirstOrDefault(x => x.Key == orderPropertyName);
if (orderProperty != null)
{
orderProperty.Value = value;
orderProperty.Save();
}
else
{
OrderProperty op = new OrderProperty
{
Key = orderPropertyName,
Value = value,
Order = purchaseOrder
};
op.Save();
}
purchaseOrder.Save();
TransactionLibrary.ExecuteBasketPipeline();
}
When I save a value using this I can see it appear against the order in the uCommerce_OrderProperty table.
However, with some properties, when I try to read them back out they are missing:
public string GetOrderProperty(string orderPropertyName)
{
PurchaseOrder purchaseOrder;
using (new CacheDisabler())
{
purchaseOrder = TransactionLibrary.GetBasket().PurchaseOrder;
}
OrderProperty orderProperty = purchaseOrder.OrderProperties.FirstOrDefault(x => x.Key == orderPropertyName);
if (orderProperty != null)
{
return orderProperty.Value;
}
return string.Empty;
}
I have also tried this code from the uCommerce site:
public string GetOrderProperty(string orderPropertyName)
{
PurchaseOrder purchaseOrder = SiteContext.Current.OrderContext.GetBasket().PurchaseOrder;
return purchaseOrder[orderPropertyName];
}
If I inspect purchaseOrder I can see the OrderProperties are missing. I have 7 properties at any one time but purchaseOrder only ever seems to have a max of 5 even though there is 7 in the table.
These are Order Properties and not Order Line Properties. Can anyone give me any pointers as to why I am seeing this behaviour?
EDIT
This line does get the value I am looking for:
OrderProperty op = OrderProperty.FirstOrDefault(x => x.Order.OrderId == purchaseOrder.OrderId && x.Key == orderPropertyName);
Even when this line (called the line after) returns null:
OrderProperty orderProperty = purchaseOrder.OrderProperties.FirstOrDefault(x => x.Key == orderPropertyName);
(Both are looking for the same Order Property)
Can anyone tell me why?
I have a comment, but I'm not allowed because of missing reputation.
Everything seems to be fine regarding your code. Can I persuade you to show the
uCommerce_OrderProperty table?
- I just want to check that the OrderLineId column is empty for you order properties.
You should be able to set and get it like this:
var property = order[orderPropertyName];
order[orderPropertyName] = "VALUE";
Regards
Mads
We also recommend that Ucommerce related question is posted at http://eureka.ucommerce.net/, the response time is often faster.

Unique Indexes convention in EF6

How to create custom index and key conventions for different type of indexes. I need different naming for following key or index types:
PK_TableName Primary keys
FK_SourceTable_Column_TargetTable for foreign keys
IX_TableName_Column1_Column2 Non-unique indexes
UX_TableName_Column1_Column2 Unique indexes
By defaults, Entity Framework uses following namings:
PK_schemaname.TableName for primary keys
FK_schemaname.SourceTable_schemaname.TargetTable_Column1 for foreign keys
IX_Column1 for non-unique indexes
ColumnName for unique indexes
I've found out that I can implement IStoreModelConvention<T>, but I haven't found particular type to use as type parameter.
Moreover, there're can be Custom Code-First Conventions, but my research is ended with no results. How I can get mentioned naming rules when I use Entity Framework Code First? It can be anything: package, sample, or just direction for following researches.
Mission impossible for PK and FK. The problems is that there is no special EdmModel property/attribute/annotation for naming the store constraint - in the model they are basically represented as list of columns (properties) and the naming convention is hardcoded inside the migration builder classes. Please note that some examples mentioned in the comments are showing how to rename the FK columns (properties), not the FK constraint itself.
Luckily for indexes, although not simple, but it's possible, thanks to the IndexAttribute and IndexAnnotation. This is because the annotation (with attribute) is associated with column (entity property), and then consolidated by an internal class called ConsolidatedIndex.
So in order to achieve the goal, you have to create IStoreModelConvention<EntityType>, prepare a consolidated index info from properties similar to how ConsolidatedIndex class does it, determine the new name based on your rules for the unnamed indexes or indexes with default name generated for FK constrains by the ForeignKeyIndexConvention, and update the corresponding IndexAnnotation of the properties.
With that being said, here is the code for applying your index name convention:
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.Data.Entity.Core.Metadata.Edm;
using System.Data.Entity.Infrastructure;
using System.Data.Entity.Infrastructure.Annotations;
using System.Data.Entity.Migrations.Model;
using System.Data.Entity.ModelConfiguration.Conventions;
using System.Linq;
public class IndexNameConvention : IStoreModelConvention<EntityType>
{
public void Apply(EntityType item, DbModel model)
{
// Build index info, consolidating indexes with the same name
var indexInfo = new List<IndexInfo>();
foreach (var p in item.Properties)
{
foreach (var mp in p.MetadataProperties)
{
var a = mp.Value as IndexAnnotation;
if (a == null) continue;
foreach (var index in a.Indexes)
{
var info = index.Name != null ? indexInfo.FirstOrDefault(e => e.Name == index.Name) : null;
if (info == null)
{
info = new IndexInfo { Name = index.Name };
indexInfo.Add(info);
}
else
{
var other = info.Entries[0].Index;
if (index.IsUnique != other.IsUnique || index.IsClustered != other.IsClustered)
throw new Exception("Invalid index configuration.");
}
info.Entries.Add(new IndexEntry { Column = p, Annotation = mp, Index = index });
}
}
}
if (indexInfo.Count == 0) return;
// Generate new name where needed
var entitySet = model.StoreModel.Container.EntitySets.First(es => es.ElementType == item);
foreach (var info in indexInfo)
{
var columns = info.Entries.OrderBy(e => e.Index.Order).Select(e => e.Column.Name);
if (info.Name == null || info.Name == IndexOperation.BuildDefaultName(columns))
{
bool unique = info.Entries[0].Index.IsUnique;
var name = string.Format("{0}_{1}_{2}", unique ? "UX" : "IX", entitySet.Table, string.Join("_", columns));
if (name.Length > 128) name = name.Substring(0, 128);
if (info.Name == name) continue;
foreach (var entry in info.Entries)
{
var index = new IndexAttribute(name);
if (entry.Index.Order >= 0)
index.Order = entry.Index.Order;
if (entry.Index.IsUniqueConfigured)
index.IsUnique = entry.Index.IsUnique;
if (entry.Index.IsClusteredConfigured)
index.IsClustered = entry.Index.IsClustered;
entry.Index = index;
entry.Modified = true;
}
}
}
// Apply the changes
foreach (var g in indexInfo.SelectMany(e => e.Entries).GroupBy(e => e.Annotation))
{
if (g.Any(e => e.Modified))
g.Key.Value = new IndexAnnotation(g.Select(e => e.Index));
}
}
class IndexInfo
{
public string Name;
public List<IndexEntry> Entries = new List<IndexEntry>();
}
class IndexEntry
{
public EdmProperty Column;
public MetadataProperty Annotation;
public IndexAttribute Index;
public bool Modified;
}
}
All you need is to add it to the DbModelBuilder.Conventions in your OnModelCreating:
modelBuilder.Conventions.Add<IndexNameConvention>();

'The given key was not present in the dictionary' - but the key exists

I am currently developing a MS Dynamics CRM 2013 - Plugin.
When I try to assign a string-value to a key of a field of an entity it gives me the 'keynotfound'-exception.
This leaves me clueless, because I can verify the key is existing. The key I give is also written correctly, and the data types are compatible, too.
Here's some extra info:
I tried resolving the issue with a server reboot. Nothing.
Remote Debugging is not an option.
I swapped "retrieved.EntityCollection.Entities[i][forField]" with retrieved.EntityCollection.Entities[i]["new_name"] and everything was working fine (kind of pointing out the obvious here, but "new_name" is not the key I try to access).
The execution stops # "if (retrieved.EntityCollection.Entities[i][forField].ToString() != "" && !overwriteExisting)"
Have you got an idea to help me out?
public void GenerateNumberForEntityCollection(string target)
{
try
{
// variables for number generation
bool overwriteExisting = (bool)preImageEntity["new_overwriteexisting"];
int suffixstart = (int)preImageEntity["new_suffixstart"];
string forField= preImageEntity["new_forfield"].ToString();
string prefix = preImageEntity["new_prefix"].ToString();
string postfix = preImageEntity["new_postfix"].ToString();
string separator = preImageEntity["new_separator"].ToString();
// Build query to get all the entries
RetrieveMultipleResponse retrieved;
int PageNumber = 1;
string PagingCookie = string.Empty;
int PageSize = 5000;
string[] Columns = { forField };
QueryExpression query = new QueryExpression()
{
EntityName = target,
ColumnSet = new ColumnSet(Columns),
PageInfo = new PagingInfo()
{
PageNumber = 1,
Count = PageSize
}
};
do
{
if (PageNumber != 1)
{
query.PageInfo.PageNumber = PageNumber;
query.PageInfo.PagingCookie = PagingCookie;
}
RetrieveMultipleRequest retrieve = new RetrieveMultipleRequest();
retrieve.Query = query;
retrieved = (RetrieveMultipleResponse)service.Execute(retrieve);
// Now that all entities are retrieved, iterate through them to gen. the numbers
int i = 0;
foreach (Entity entity in retrieved.EntityCollection.Entities)
{
if (retrieved.EntityCollection.Entities[i][forField].ToString() != "" && !overwriteExisting)
{
//continue;
}
else
{
retrieved.EntityCollection.Entities[i][forField] = prefix + separator + suffixstart.ToString() + separator + postfix;
}
suffixstart++;
service.Update(retrieved.EntityCollection.Entities[i]);
i++;
}
if (retrieved.EntityCollection.MoreRecords)
{
PageNumber++;
PagingCookie = retrieved.EntityCollection.PagingCookie;
}
} while (retrieved.EntityCollection.MoreRecords);
}
catch (Exception e)
{
tracing.Trace("GenerateNumberForEntityCollection: Failed: {0}", e.ToString());
}
}
How did you verify that the key exists?
If the data in a field is null, the Entity instance will not contain that key, even if you specify it in the query's ColumnSet.
This will return you a boolean, indicating if the key exists in the Entity. You can do this control before attempting to read the attribute.
var attributeExists = retrieved.EntityCollection.Entities[i].Contains(forField)
The control below you've done will result in the exception you're getting if the field is null. Just make sure that the attribute exists before.
retrieved.EntityCollection.Entities[i][forField].ToString() != ""
Additionally, you'll get a null reference exception if no records were returned from the query. Make you do a null check on retrieved.EntityCollection.Entities.
When you are querying data in Dynamics CRM it is important to know that record fields having null values in the database are not included in the Attributes collection of the Entity instances being returned.
Getting a value from an Entity's Attribute with this construct:
var value = retrieved.EntityCollection.Entities[i][forField].ToString();
succeeds when attribute forField already has a value in the database, but fails when its current value is null.
Therefore the preferred method to get the attribute values from an entity is GetAttributeValue<T>, like this:
var value = retrieved.EntityCollection.Entities[i].getAttributeValue<string>(forField);
This method returns the value when the attribute exists in the attribute collection, otherwise it returns null.
If any of the fields among
(new_forfield,new_prefix,new_postfix,new_separator) has null value,
that column does not present in the retrieved object and you are trying to get the value of null column preImageEntity["new_forfield"] which will throw keynotfound'-exception ,
so change the code
string forField= preImageEntity["new_forfield"].ToString();
string prefix = preImageEntity["new_prefix"].ToString();
string postfix = preImageEntity["new_postfix"].ToString();
string separator = preImageEntity["new_separator"].ToString();
to
string forField = preImageEntity.Attributes.Contains("new_forfield")? preImageEntity["new_forfield"].ToString():"";
string prefix = preImageEntity.Attributes.Contains("new_forfield") ? preImageEntity["new_prefix"].ToString() : "";
string postfix = preImageEntity.Attributes.Contains("new_forfield") ? preImageEntity["new_postfix"].ToString() : "";
string separator = preImageEntity.Attributes.Contains("new_forfield") ? preImageEntity["new_separator"].ToString() : "";
this will check for field, if it exists than will parse the value to
string else will assign empty string.

how to could use reflection to write to the appropriate properties on the entity?

How to: you don't want to manually write code to write to ColumnA, ColumnB, etc, then you could use reflection to write to the appropriate properties on the entity?
you can create a new instance of this class and its property values. These property values are mapped to columns in the SQL database table. You then pass this object to the DataContext class generated by LINQ to SQL, to add a new row to the table in the database.
So, you would do something like this:
For a database table with columns "ColumnA", "ColumnB" and "ColumnC"
var myEntity = new EntityObject { ColumnA = "valueA", ColumnB = "valueB", "ColumnC" = "valueC" };
DataContext.InsertOnSubmit(myEntity);
DataContext.SubmitChanges();
This will insert a new row into the database with the column values specified.
Now if you don't want to manually write code to write to ColumnA, ColumnB, etc, then you could use reflection to write to the appropriate properties on the entity:
For example, with entity instance 'myEntity':
var properties = myEntity.GetType().GetProperties();
foreach (string ky in ld.Keys)
{
var matchingProperty = properties.Where(p => p.Name.Equals(ky)).FirstOrDefault();
if (matchingProperty != null)
{
matchingProperty.SetValue(myEntity, ld[ky], null);
}
}
i try to this but i cannot. How can you make it?
Check this article : LINQ to SQL: All common operations (Insert, Update, Delete, Get) in one base class
Following might help you :
protected virtual void Update(T entity, Expression<Func<T, bool>> query)
{
using (DC db = new DC())
{
object propertyValue = null;
T entityFromDB = db.GetTable<T>().Where(query).SingleOrDefault();
if (null == entityFromDB)
throw new NullReferenceException("Query Supplied to " +
"Get entity from DB is invalid, NULL value returned");
PropertyInfo[] properties = entityFromDB.GetType().GetProperties();
foreach (PropertyInfo property in properties)
{
propertyValue = null;
if (null != property.GetSetMethod())
{
PropertyInfo entityProperty =
entity.GetType().GetProperty(property.Name);
if (entityProperty.PropertyType.BaseType ==
Type.GetType("System.ValueType")||
entityProperty.PropertyType ==
Type.GetType("System.String"))
propertyValue =
entity.GetType().GetProperty(property.Name).GetValue(entity, null);
if (null != propertyValue)
property.SetValue(entityFromDB, propertyValue, null);
}
}
db.SubmitChanges();
}
}

Categories