Updating an entity with a List - c#

Controller
...
// field will represent a previously filled up field
Field field = this.unitOfWork.FieldRepository.GetByID( fieldID );
// But I want to remove some of these things called
// field attributes from its array, so I empty it out,
// and refill the list
field.FieldAttributes = null;
field.FieldAttributes = new List<FieldAttribute>();
foreach (var fieldAttributeGroup in fieldAttributes)
{
foreach (var fieldAttributeID in fieldAttributeGroup.Attributes)
{
if (fieldAttributeID != "false" && fieldAttributeID != null)
{
// dbFieldAttribute is from the database
FieldAttribute dbFieldAttribute = this.unitOfWork.FieldAttributeRepository.GetByID(Convert.ToInt32(fieldAttributeID));
// I fill the new field up
field.FieldAttributes.Add(dbFieldAttribute);
}
}
}
// I send the field to the repository:
this.unitOfWork.FieldRepository.Update( field );
Repository
public void Update( Field field )
{
Field dbEntity = context.Fields.Find( field.FieldID );
dbEntity.FieldAttributes = field.FieldAttributes;
context.SaveChanges();
The problem:
Existing field attribute relationships that were attached to a field in the database remain attached even though I have set that attribute equal to the new set of field attributes.
The question:
Do I have to remove entries that existed... explicitly or is there a way to make Entity framework handle all of the relationship modifications.. obviously some existing field attribute relationships are not explicitly remove at this point.

Related

Dynamics 365 (CRM) Version 9.1 online C# code Error: Entity Id must be the same as the value set in property bag

When cloning/copying child records, I use a foreach loop and then create a record with all its attributes. I wrote similar code in another project and worked fine for me.
There are multiple articles/questions based on the same Error. Now my issue is how should I create child records with all its attributes.
foreach (var packingList in oPEntityCollection.Entities)
{
packingList.Attributes.Remove("statuscode");
packingList.Attributes.Remove("statecode");
packingList.Id=Guid.Empty;
orgService.Create(packingList);
}
Another strange issue
An entry with the same key already exists
Code:
Entity parentEntity = orgService.Retrieve(context.PrimaryEntityName, context.PrimaryEntityId, new ColumnSet(true));
parentEntity.Id = Guid.empty;
orgService.Create(parentEntity);
Even if I create a new object and copy parentEntity just like below I get this error.
Entity costcalEntity = new Entity();
costcalEntity = parentEntity;
costcalEntity.Id = Guid.Empty;
orgService.Create(costcalEntity);
So I end up creating a record with primary name and once the record is created, I update the same record with old record attributes.
Entity costcalEntity = new Entity();
costcalEntity.LogicalName = parentEntity.LogicalName;
costcalEntity["name"] = parentQuotationEntity.GetAttributeValue<string>("name");
costcalEntity.Id = Guid.Empty;
Guid newGuid = orgService.Create(costcalEntity);
if (newGuid != Guid.Empty)
{
costcalEntity = parentEntity;
costcalEntity.Id = newGuid;
orgService.Update(costcalEntity);
}
and this works fine.
In both cases you have the same issue, with it's root cause being the Id stored in the attribute collection of the entity. If you look at the early bound generation, you can access the Id by the entity.Id property, as well as the attribute collection as shown in the definition for the id in the primary id:
public System.Nullable<System.Guid> AccountId
{
[System.Diagnostics.DebuggerNonUserCode()]
get
{
return this.GetAttributeValue<System.Nullable<System.Guid>>("accountid");
}
[System.Diagnostics.DebuggerNonUserCode()]
set
{
this.OnPropertyChanging("AccountId");
this.SetAttributeValue("accountid", value);
if (value.HasValue)
{
base.Id = value.Value;
}
else
{
base.Id = System.Guid.Empty;
}
this.OnPropertyChanged("AccountId");
}
}
So when you are retrieving an existing entity, both the Property Id, which you have handled, as well as the attribute collection, which you haven't handled, have been populated by the CRM SDK. So in order to be able to duplicate it, you'll need to clear the id in both places.
Here is how I solved it
foreach (Entity packingList in oPEntityCollection.Entities)
{
Entity newpackingList = new Entity()
{
LogicalName = packingList.LogicalName,
};
newpackingList.Attributes.AddRange(packingList.Attributes);
newpackingList.Attributes.Remove("primaryGuid");
Guid newOpGuid = orgService.Create(newpackingList);
tracingService.Trace($"OP record created sucessfully with guid {newOpGuid}");
}
So the Trick, issue was rather I was trying to assign packingList directly to newpackingList. This caused to assign packingList metadata attributes as well such. This was not acceptable with crm
But rather I should add it's attribute. This worked and created all child records.
Same worked for parent record as well
Entity parentEntity = orgService.Retrieve(context.PrimaryEntityName, context.PrimaryEntityId, new ColumnSet(true));
Entity newParentEntity = new Entity()
{
LogicalName = parentEntity.LogicalName,
};
newParentEntity.Attributes.AddRange(parentEntity.Attributes);
newParentEntity.Attributes.Remove("primaryGuid");
orgService.Create(newParentEntity);
If your question is "How do I duplicate an Entity retrieved from CRM?", your answer can be simplified.
var entity = orgService.Retrieve(context.PrimaryEntityName, context.PrimaryEntityId, new ColumnSet(true));
entity.Id = Guid.Empty;
entity.Attributes.Remove("primaryGuid");
orgService.Create(entity);

Create New Object From DataTable using field Mapping

I have a table where i store data mappings. My model for that looks like so
public class MapperTable
{
public string EE_First_Name {get; set;}
public string EE_Last_Name {get; set;}
public string EE_MI {get; set;}
}
The purpose of this table is to store mappings from a csv so that i can then create an object with those fields.
So that if the first name field in a csv is FirstName* it is matched to my table and then i create a new object with the value from FirstName* and set the field name to EE_First_Name
Im saving the mappings via Id and when the user selects an Id I use that particular mapping to map the data from the csv.
public MapperTableConvertCsvUsingMap(DataTable csv){
var namesFromColumnCsvMap = DataAccess.ExportCsvMaps.FindByExp(x => x.ConfigId == idINt).FirstOrDefault();
foreach(DataRow row in Csv.Rows)
{
var csvMapped = new MapperTable {
EE_First_Name = row[namesFromColumnCsvMap.EE_First_Name__.TrimEnd()].ToString(),
EE_Last_Name__ = row[namesFromColumnCsvMap.EE_Last_Name__.TrimEnd()].ToString(),
EE_MI = row[namesFromColumnCsvMap.EE_MI.TrimEnd()].ToString(),
};
}
This works if all the columns are mapped correctly only. If they don't match exactly it blows up. What would be a better way to match the column headers from the csv to the column header definitions stored in my table to create a new object?
I have a csv that looks like this
It needs to look like this
The column mapping needs to get stored in the database so that the process can be repeated. The incoming csv may be different than the one shown but should be able to be mapped to the final one using the stored values in the database.
Check first that the column exists in the table/csv to be mapped before trying to access it. You can then access the column if it exists or return some default value like empty string "" or something else.
public MapperTable[] ConvertCsvUsingMap(DataTable csv) {
var namesFromColumnCsvMap = DataAccess.ExportCsvMaps.FindByExp(x => x.ConfigId == idINt).FirstOrDefault();
foreach (DataRow row in csv.Rows) {
var csvMapped = new MapperTable {
EE_First_Name = namesFromColumnCsvMap.EE_First_Name != null && csv.Columns.Contains(namesFromColumnCsvMap.EE_First_Name.TrimEnd()) ? row[namesFromColumnCsvMap.EE_First_Name.TrimEnd()].ToString() : "",
EE_Last_Name = namesFromColumnCsvMap.EE_Last_Name != null csv.Columns.Contains(namesFromColumnCsvMap.EE_Last_Name.TrimEnd()) ? row[namesFromColumnCsvMap.EE_Last_Name.TrimEnd()].ToString() : "",
EE_MI = namesFromColumnCsvMap.EE_MI != null && csv.Columns.Contains(namesFromColumnCsvMap.EE_MI.TrimEnd()) ? row[namesFromColumnCsvMap.EE_MI.TrimEnd()].ToString() : "",
};
//...
}
}
The code above checks first that the mapped column name exists in the table to be mapped before trying to get the value in the row. If it does not find a match it sets the mapped object property to an empty string.

c# controlling program flow l by object type while in a loop

I am working with a system which allows users to add C.V.s to a company database. A document consists of sections and for each section there can be one or more fields. A field corresponds to a user control which is mapped to a specific field type. The user controls are not bound to the document object in any way and to save data to the document from the page containing the user controls there is a method like this:
public void SaveData(Document document)
{
// Get all user controls on the page
var userControls = FindDescendants<UserControl>(this);
foreach (var section in document.Sections)
{
foreach (var field in section.Fields)
{
if (field is Address)
{
var address = field as Address;
var addressControl = userControls.FirstOrDefault(o => o.ClientID.Contains(field.Id)) as AddressUserControl;
addressControl.SaveData(address);
}
else if (field is TelephoneNumbers)
{
var telephoneNumbers = field as TelephoneNumbers;
var telephoneNumbersControl = userControls.FirstOrDefault(o => o.ClientID.Contains(field.Id)) as TelephoneNumbersUserControl;
telephoneNumbersControl.SaveData(telephoneNumbers);
}
...
...
else if (field is Employment)
{
var employment = field as Employment;
var employmentControl = userControls.FirstOrDefault(o => o.ClientID.Contains(field.Id)) as EmploymentUserControl;
employmentControl.SaveData(employment);
}
}
}
}
I do not like the nested loops and each time a new field type is added the conditional statement within the foreach grows.
Any ideas how this could be structured more elegantly?

How can I edit or add to a particular field without pull the all object

How I can do just this ( a.myFavorits.Add()) without pulling the all object to var a , because a has a lot of data, and I don't want to pull all a object, but I can't find a way do do it.
I want to do the lambada and the linq without return something but linq is always return something
public static void addFavorits(long f,long idUser)
{
using (var db = dataBase())
{
// here i pull object user from users table
var a = db.users.Where(c => c.id == idUser).SingleOrDefault();
// here i adding to the object field myFavorits new value
//myFavorits is also a table of entitys that connected to user object
a.myFavorits.Add(new BE.FavoritsUsersLong { myLong = f });
db.SaveChanges();
}
}
I thought to do something like this but i dont know how to set the field users_TableId that is the key that connect the 2 tables
public static void addFavorits(long favoritId,long idUser)
{
using (var db = dataBase())
{
db.favoritsUsersLong.Add(new BE.FavoritsUsersLong {myLong = favoritId}
/*,users_TableId =idUser*/);
db.SaveChanges();
}
}
Here's a concrete example that does what you want. In this example, only the Name of a Company is modified and saved. Or an item is added to one of its collections.
var cmp = new Company{ CmpId = 1, Name = "Cmp1" }; // CmpId is the primary key
db.Companies.Attach(cmp);
db.Entry(cmp).Property(c => c.Name).IsModified = true;
// Or add an entity to a collection:
cmp.Users = new[] {new User { Name = "a1", PassWord = "a1" } };
try
{
db.Configuration.ValidateOnSaveEnabled = false;
db.SaveChanges();
}
finally
{
db.Configuration.ValidateOnSaveEnabled = true;
}
Result in SQL:
DECLARE #0 VarChar(30) = 'Cmp1'
DECLARE #1 Int = 1
UPDATE [dbo].[Company]
SET [Name] = #0
WHERE ([CmpId] = #1)
There are a few things to note here:
Obviously you need to know the Id of the entity you want to modify.
The object you create is called a stub entity, which is an incomplete entity. When you try to save such an entity, EF is very likely to complain about null values in required properties. That's why almost certain you'd have to disable validation (temporarily, or, better, dispose the context immediately).
If you want to add an item to a collection, you should leave validation enabled, because you'd want to know for sure that the new entity is valid. So you shouldn't mix these two ways to use a stub entity.
If you often need roughly the same small part of your entity you may consider table splitting.
I'm guessing this is what you want? I don't see you 'editting' I only see you adding.
using (var db = dataBase())
{
var a = new user();
....
//set properties etc..
...
a.myFavorits.Add(new BE.FavoritsUsersLong { myLong = f });
db.users.Add(a);
db.SaveChanges();
}

How delete collection?

var main = (from d in db.Discounts where d.Id == discount.Id && d.UserId == userId select d).FirstOrDefault();
main.Address = "new address";
main.TermsStocks.Clear(); // I need clear collection and add new object.
foreach (var termsStock in terms)
{
main.TermsStocks.Add(new TermsStock() { Id = Guid.NewGuid(), MainId = main.Id, Title = termsStock.Title });
}
db.SaveChanges();
and I have error:
The operation failed. Unable to change the link because one or more
properties of the foreign key values ​​do not allow NULL. If you
change the connection property of the respective foreign key set to
NULL. If the foreign key value does not support NULL, must be defined
a new relationship, the foreign key property should be set to another
value other than NULL, or to remove unbound object.
how to remove the entire collection and save a new one?
You will have to remove those items form the database as well, not just from the nav property.
Roughly:
// main.TermsStocks.Clear();
foreach (var item in main.TermsStocks)
{
db.TermsStocks.Remove(item);
}
I think TermsStock object has a non nullable foreign key of Discount. Then it is not possible to have a TermsStock object without a Discount. What you are trying to do is not possible in this manner.

Categories

Resources