I have an entity Contracts like this:
public partial class Contracts
{
public Contracts()
{
ListKindWorks = new HashSet<ListKindWorks>();
ListSubjects = new HashSet<ListSubjects>();
}
public int Id { get; set; }
public string Num { get; set; }
public DateTime DateConclusion { get; set; }
public int Worker { get; set; }
public DateTime DateStartWork { get; set; }
public DateTime DateEndWork { get; set; }
public float Salary { get; set; }
public virtual Workers WorkerNavigation { get; set; }
public virtual ICollection<ListKindWorks> ListKindWorks { get; set; }
public virtual ICollection<ListSubjects> ListSubjects { get; set; }
}
And function ShowUpdateDialog():
/// <summary>
/// Open dialog for update chosen Contract
/// </summary>
/// <param name="c">chosen Contract</param>
internal void ShowUpdateDialog(Contracts c)
{
Contract = c;
using (ContractForm form = new ContractForm())
{
form.Fill(model.Data);
form.Fill(Contract);
form.Fill(model.GetUI(Mode.UPDATE));
if (form.ShowDialog() == DialogResult.OK)
{
bool result = true;
try
{
using (ModelContext context = new ModelContext())
{
context.Attach(Contract);
Contract = form.GetMainValues(Contract);
Contract = form.GetDetailValues(Contract);
context.SaveChanges();
}
}
catch (Exception ex)
{
result = false;
string msg = string.Format("Ошибка во время обновления записи в базе данных. Детали: {0}", ex.Message);
form.ShowError(msg);
}
if (result)
{
ContractUpdatedSuccessEvent?.Invoke(this, EventArgs.Empty);
}
}
}
}
External variable:
public Contracts Contract { get; set; }
It is used not to allocate memory every time and is public, so that in case of a successful update another class can take it and insert into the DataGridView. Therefore I'm not accessing the database for the current values of the record, because the data comes from the DataGridView. To track changes used context.Attach(Contract).
internal Contracts GetMainValues(Contracts c)
{
c.Num = tbNum.Text;
c.Salary = float.Parse(tbSalary.Text);
c.DateConclusion = dpDateConclusion.Value;
c.DateStartWork = dpDateStart.Value;
c.DateEndWork = dpDateEnd.Value;
Item item = (Item)cbWorker.SelectedItem;
c.Worker = item.Id;
return c;
}
internal Contracts GetDetailValues(Contracts c)
{
listKindWorks.Clear();
listSelectedSubjects.Clear();
foreach (int index in clbKindWork.CheckedIndices)
{
int id = ((Item)clbKindWork.Items[index]).Id;
ListKindWorks item = new ListKindWorks
{
IdContract = c.Id,
IdKindWork = id
};
listKindWorks.Add(item);
}
foreach (Item item in lbSelectedSubject.Items)
{
ListSubjects subject = new ListSubjects
{
IdContract = c.Id,
IdSubject = item.Id
};
listSelectedSubjects.Add(subject);
}
c.ListKindWorks = listKindWorks;
c.ListSubjects = listSelectedSubjects;
return c;
}
My problem is as follows:
When if (.. == DialogResult.OK) is true, I need to update current Contract to new values from a form like this:
context.Attach(Contract);
Contract = form.GetMainValues(Contract);
Contract = form.GetDetailValues(Contract);
context.SaveChanges();
But therefore this code must be into using (ContractForm..). Otherwise impossible to get new values from form. If I create a new variable like Contracts and separate function Update like this:
private void Update(Contracts c)
{
using (ModelContext context = new ModelContext())
{
context.Attach(Contract);
Contract = c;
context.SaveChanges();
}
}
No update occurs for values from GetDetailValues(). Why?
Can I simplify this code?
Update:
According code from answer Henk Holterman and my private void Update(Contracts c):
changed values from GetMainValues(), but no from GetDetailValues()
I added to Contracts method Update() and call it after .Attach()
internal void Update(Contracts c)
{
this.Num = c.Num;
this.Salary = c.Salary;
this.Worker = c.Worker;
this.FullName = c.FullName;
this.DateConclusion = c.DateConclusion;
this.DateStartWork = c.DateStartWork;
this.DateEndWork = c.DateEndWork;
this.ListKindWorks = c.ListKindWorks;
this.ListSubjects = c.ListSubjects;
}
private void Update(Contracts oldValue, Contracts newValue)
{
using (ModelContext context = new ModelContext())
{
context.Attach(oldValue);
oldValue.Update(newValue);
// oldValue = newValue; // .Attach does not respond to this
context.SaveChanges();
}
}
Related
I have to an upper class with nested classes
public class Preferences
{
public FunctionClass function { get; set; } = new FunctionClass();
public class FunctionClass
{
public string programfolder { get; set; } = "";
...
}
public LoggerClass logger { get; set; } = new LoggerClass();
public class LoggerClass
{
public string logFolder { get; set; } = "Log";
...
}
public OptionClass options { get; set; } = new OptionClass();
public class OptionClass
{
public bool showGraphics { get; set; } = true;
...
}
public MqttSpSetupClass MqttSpSetup { get; set; } = new MqttSpSetupClass();
public class MqttSpSetupClass
{
public string strAddress { get; set; } = "localhost";
...
}
}
so I want reflection to cycle on all member of each inner class
PropertyInfo[] props_Outer = typeof(IoAppPreferences).GetProperties();
int counter = 0;
foreach (PropertyInfo prop_Upper in props_Outer)
{
var sName_Outer = prop_Upper.Name;
var val_Outer = props_Outer.GetValue(counter ++);
PropertyInfo[] properties_Inner;
switch (sName_Outer.ToUpper())
{
case "DIMS": properties_Inner = typeof(IoAppPreferences.DimsClass).GetProperties(); break;
...
}
foreach (PropertyInfo prop_Inner in properties_Inner)
{
var sName = prop_Inner.Name;
//prefs.function
var sVal = prop_Inner.GetValue(val_Outer);<------ERROR
switch (prop_Inner.Name.ToUpper())
{
...
}
}
I get an error where I put the arrow. And the reason is that val_Outer is FunctionClass function while if I hardcode prefs.function it is ok. Of course, I can put a switch per each one, but my question is: is there a better way to solve it?
I have seen this solution but can't fit to my needs
You got error because val_Outer is wrong instance. You are trying to get value out of counter integer props_Outer.GetValue(counter ++)
If your goal is to get property values from nested classes you must have instance of Preferences object:
var appPreferences = new Preferences();
var propsOuter = appPreferences.GetType().GetProperties();
foreach (var po in propsOuter)
{
var valueOuter = po.GetValue(appPreferences);
Console.WriteLine($"{po.Name}");
if (valueOuter == null) continue;
var propsInner = valueOuter.GetType().GetProperties();
foreach (var pi in propsInner)
{
var valueInner = pi.GetValue(valueOuter);
Console.WriteLine($"{pi.Name}: {valueInner}");
}
}
But getting values through reflection is pretty much useless if you already have object instance.
Hello and still happy Ney Year
I would like to ask you for initial aid. My goal is to write a parser (e.g. source file is a bmecat-xml file and target is an Excel-file) that is dynamic and flexible enough to handle data-conversion even when sourcefile-content changes or user would require additional transformation of data.
I wrote the first part of the parser which loads data from the source-bmecat-file into corresponding classes. The class structure is exposed to the user (by reflection) and the user can map source-fields to target fields.
Where I get stuck is at the moment, when additional logic / conversion needs to be incorporated.
I think Scripting would help me to solve this. the mapping data (source field to target field) could contain an additional script that would be executed dynamically (and hence must have access to application data, especially classes which hold sourcefile and targetfile data).
It would be really great if you could point me to the right direction, to a point, where I can start from.
Thank you very much!
sample-code
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Reflection;
namespace ScriptingDemoProject
{
class DataClass
{
TargetData target;
SourceData source;
MappingData map;
public DataClass()
{
target = new TargetData();
source = new SourceData();
map = new MappingData();
// generate sample data
GenerateData();
// copy source data to target data
ExecuteMapping();
}
public TargetData TargetDataInfo
{ get { return target; } }
public SourceData SourceDataInfo
{ get { return source; } }
public MappingData MappingDataInfo
{ get { return map; } }
private void GenerateData()
{
// add sourcedata
source.Header.DefaultLanguage = "deu";
source.RecipientID = "recipient...";
source.SenderID = "sender...";
SourceItem item = new SourceItem();
item.ItemID = "Item1";
item.ItemNames.AddRange( new List<SourceItemName>() {
new SourceItemName { ItemName = "Item1NameGerman", Languauge = "deu" },
new SourceItemName { ItemName = "Item1NameFrench", Languauge = "fra" }
});
source.Items.Add(item);
// add targetdata
target.AddRec(new List<TargetField>()
{
new TargetField { ColumnID=0, FieldName="ItemNo", FieldValue="Item1"},
new TargetField { ColumnID=1, FieldName="DescrGerman", FieldValue=""},
new TargetField { ColumnID=2, FieldName="DescrFrench", FieldValue=""}
});
target.AddRec(new List<TargetField>()
{
new TargetField { ColumnID=0, FieldName="ItemNo", FieldValue="Item2"},
new TargetField { ColumnID=1, FieldName="DescrGerman", FieldValue=""},
new TargetField { ColumnID=2, FieldName="DescrFrench", FieldValue=""}
});
// add mappinginstructions
map.TargetKeyFieldIndex = 0;
map.MappingFieldInfo.AddRange(new List<MappingFields>() {
new MappingFields { SourceFieldMapping="ItemName", TargetFieldMapping=1, ScriptMapping=#"... where Language=""ger""" },
new MappingFields { SourceFieldMapping="ItemName", TargetFieldMapping=2, ScriptMapping=#"... where Language=""fra""" }
});
// get properties, e.g.
var pInfo = source.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance);
}
private void ExecuteMapping()
{
// get target records
foreach (var targetRec in TargetDataInfo.TargetRecords)
{
// get key field value
string itemNo = targetRec.Where(x => x.ColumnID == map.TargetKeyFieldIndex).FirstOrDefault().FieldValue;
// get source item
SourceItem srcItem = SourceDataInfo.Items.Where(x => x.ItemID == itemNo).FirstOrDefault();
if (srcItem == null)
continue;
// get mapping instructions
foreach (var mapInstruction in map.MappingFieldInfo)
{
// i'd like to have two options
// option 1: use script
// option 2: use reflection
// option 1: script
// script will be executed at runtime and gets value from srcItem and sets value in targetRec
string script = mapInstruction.ScriptMapping;
// script would contain / execute the following statements:
TargetField field = targetRec.Where(x => x.ColumnID == mapInstruction.TargetFieldMapping).FirstOrDefault();
field.FieldValue = srcItem.ItemNames.Where(x => x.Languauge == "deu").FirstOrDefault().ItemName;
// option 2: get value by reflection
// e.g.
// though don't know how to handle List<Class>
PropertyInfo pi = SourceDataInfo.GetType().GetProperty("SenderID");
object val = pi.GetValue(SourceDataInfo, null);
// ...
}
}
}
}
public class MappingData
{
List<MappingFields> mappingFields;
public MappingData ()
{
mappingFields = new List<MappingFields>();
}
public int TargetKeyFieldIndex { get; set; }
public List<MappingFields> MappingFieldInfo
{ get { return mappingFields; } }
}
public class MappingFields
{
public string SourceFieldMapping { get; set; }
public int TargetFieldMapping { get; set; }
public string ScriptMapping { get; set; }
}
public class TargetData
{
private List<List<TargetField>> targetRecords;
public TargetData()
{
targetRecords = new List<List<TargetField>>();
}
public List<List<TargetField>> TargetRecords
{ get { return targetRecords; } }
public void AddRec(List<TargetField> TargetFields)
{
targetRecords.Add(TargetFields);
}
}
public class TargetField
{
public string FieldName
{ get; set; }
public int ColumnID
{ get; set; }
public string FieldValue
{ get; set; }
}
public class SourceData
{
private List<SourceItem> sourceItems;
private SourceHeader sourceHeader;
public SourceData()
{
sourceHeader = new SourceHeader();
sourceItems = new List<SourceItem>();
}
public SourceHeader Header
{ get { return sourceHeader; } }
public List<SourceItem> Items
{ get { return sourceItems; } }
public string SenderID
{ get; set; }
public string RecipientID
{ get; set; }
}
public class SourceHeader
{
public string DefaultLanguage
{ get; set; }
}
public class SourceItem
{
private List<SourceItemName> itemNames;
public SourceItem()
{
itemNames = new List<SourceItemName>();
}
public string ItemID
{ get; set; }
public List<SourceItemName> ItemNames
{ get { return itemNames; } }
public SourceItemName GetNameByLang(string Lang)
{
return itemNames.Where(x => x.Languauge == Lang).FirstOrDefault();
}
}
public class SourceItemName
{
public string ItemName
{ get; set; }
public string Languauge
{ get; set; }
}
}
I'm using MVVM Light in Visual Studio 2015 to build an WPF app. There's a method in the code that's repeated with slight variation 4 times in the code; the only difference is the type of the ObservableCollection being modified and the method called on the data service layer.
Here's the method, which returns an ObservableCollection of StatusViewModel objects, which are used to populate a ComboBox; the StatusVm is used for binding to the SelectedItem of the ComboBox, is set as the first item in the collection and is "blank":
private async Task<ObservableCollection<StatusViewModel>> GetStatuses()
{
var result = new ObservableCollection<StatusViewModel>();
var blank = new StatusViewModel
{
StatusId = -1,
Status = null,
Description = null,
IsActive = false,
CreatedDate = DateTime.Now
};
result.Add(blank);
var dataService = new MyDataService();
foreach (var c in await dataService.GetStatuses())
result.Add(c);
StatusVm =
result.SingleOrDefault(c => c.StatusId.Equals(-1));
return result;
}
Here's the private field and public property for StatusVm:
private StatusViewModel _statusVm;
public StatusViewModel StatusVm
{
get { return _statusVm; }
set
{
if (Equals(value, _statusVm)) return;
_statusVm = value;
RaisePropertyChanged();
}
}
Now imagine the above being repeated 3 more times, with 3 more VM types! How do I make GetStatuses() into a method that can take different view model types and call the appropriate method on the data service? Thank you.
Update: Here are the property and method for another of the types:
private MroViewModel_mroVm;
public MroViewModel MroVm
{
get { return _mroVm; }
set
{
if (Equals(value, _mroVm)) return;
_mroVm = value;
RaisePropertyChanged();
}
}
private async Task<ObservableCollection<MroViewModel>> GetMro()
{
var result = new ObservableCollection<MroViewModel>();
var blank = new MroViewModel
{
StatusId = -1,
Status = null,
Description = null,
IsActive = false,
CreatedDate = DateTime.Now
};
result.Add(blank);
var dataService = new MyDataService();
foreach (var c in await dataService.GetMro())
result.Add(c);
MroVm =
result.SingleOrDefault(c => c.StatusId.Equals(-1));
return result;
}
You would create interface for common properties.
internal interface IStatusViewModel {
int StatusId { get; set; }
string Status { get; set; }
string Description { get; set; }
bool IsActive { get; set; }
DateTime CreatedDate { get; set; }
}
Implement the interface in classes that you need to retrieve status for
internal class MroViewModel : IStatusViewModel {
public int StatusId { get; set; }
public string Status { get; set; }
public string Description { get; set; }
public bool IsActive { get; set; }
public DateTime CreatedDate { get; set; }
}
Make method static and pass the service function which will call appropriate method to retrieve old statuses.
public static async Task<ObservableCollection<T>> GetStatuses<T>(
Func<MyDataService, Task<IEnumerable<T>>> retrieveStatusesAction)
where T : IStatusViewModel, new()
{
var result = new ObservableCollection<T>();
var blank = new T
{
StatusId = -1,
Status = null,
Description = null,
IsActive = false,
CreatedDate = DateTime.Now
};
result.Add(blank);
var dataService = new MyDataService();
foreach (var c in await retrieveStatusesAction(dataService))
result.Add(c);
// TODO Implement Expression<Func<TSource, TResult>> projection for assigning to VM
StatusVm = result.SingleOrDefault(c => c.StatusId.Equals(-1));
return result;
}
You would then call this method like so:
GetStatuses((service) => service.GetMro());
I didn't test this and the StatusVm needs to be assigned using expression compilation. I will take a look at how to do that now, but the idea is there.
For the Expression and property assigning:
Property selector Expression<Func<T>>. How to get/set value to selected property
-- EDIT --
Something like this for VM assignment:
public static async Task<ObservableCollection<T>> GetStatuses<T, TContainer>(
TContainer instance,
Expression<Func<TContainer, T>> viewModelProjection,
Func<MyDataService, Task<IEnumerable<T>>> retrieveStatusesAction)
where T : IStatusViewModel, new()
{
var result = new ObservableCollection<T>();
var blank = new T
{
StatusId = -1,
Status = null,
Description = null,
IsActive = false,
CreatedDate = DateTime.Now
};
result.Add(blank);
var dataService = new MyDataService();
foreach (var c in await retrieveStatusesAction(dataService))
result.Add(c);
var vmStatus = result.SingleOrDefault(c => c.StatusId.Equals(-1));
// Warning: Check casted values, this is unsafe
var vm = (PropertyInfo)((MemberExpression)viewModelProjection.Body).Member;
vm.SetValue(instance, vmStatus, null);
return result;
}
You would then call the method like this:
await GetStatuses(this, inst => inst.MroVm, (service) => service.GetMro());
If you are not familiar with expressions, I will explain.
First argument is object in which view model instance is located. Second argument selects the property from that object that corresponds to the view model that needs to be changed. Last argument is function that takes the service and returns appropriate method that retrieves the statuses - this is like pointer to the function in C++.
This will compile, but not sure if it will behave as expected. It should. If you have any problems, please write them down in comments.
You could define interfaces and try a combo of Strategy and Factory, like the following (I skipped async/await for simplicity):
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Starting");
var mainVm = new MainViewModel();
mainVm.GetStatuses();
mainVm.GetMro();
Console.WriteLine("Status: {0} {1}", mainVm.StatusVm.Name, mainVm.StatusVm.CreateDate);
Console.WriteLine("MroVm: {0} {1}", mainVm.MroVm.Name, mainVm.MroVm.CreateDate);
}
}
public class MainViewModel
{
public StatusViewModel StatusVm { get; set; }
public MroViewModel MroVm { get; set; }
public void GetStatuses()
{
var result = Get(VmKind.Status);
StatusVm = result.SingleOrDefault(c => c.StatusId.Equals(-1)) as StatusViewModel;
}
public void GetMro()
{
var result = Get(VmKind.Mro);
MroVm = result.SingleOrDefault(c => c.StatusId.Equals(-1)) as MroViewModel;
}
public IEnumerable<IVm> Get(VmKind vmKind)
{
var dataService = new MyDataService();
return dataService.Get(vmKind);
}
}
public interface IVm
{
int StatusId { get; set; }
DateTime CreateDate { get; set; }
string Name { get; }
}
public class StatusViewModel : IVm
{
public DateTime CreateDate { get; set; }
public int StatusId { get; set; }
public string Name { get { return "StatusViewModel"; } }
}
public class MroViewModel : IVm
{
public DateTime CreateDate { get; set; }
public int StatusId { get; set; }
public string Name { get { return "MroViewModel"; } }
}
public enum VmKind {Status, Mro }
#region Strategy
public interface IDataGetter
{
IEnumerable<IVm> Get(VmKind vmKind);
}
public class MyDataService : IDataGetter {
public IEnumerable<IVm> Get(VmKind vmKind)
{
switch (vmKind)
{
case VmKind.Status:
return GetStatuses();
//break;
case VmKind.Mro:
return GetMro();
//break;
default:
throw new ArgumentException("Unknown VM type");
}
}
private IEnumerable<IVm> GetMro()
{
return new List<MroViewModel> {
new MroViewModel { StatusId = -1, CreateDate = DateTime.Now },
new MroViewModel { StatusId = 2, CreateDate = DateTime.Now }
};
}
private IEnumerable<StatusViewModel> GetStatuses()
{
return new List<StatusViewModel> {
new StatusViewModel { StatusId = -1, CreateDate = DateTime.Now },
new StatusViewModel { StatusId = 2, CreateDate = DateTime.Now }
};
}
}
#endregion
#region Factory
public class VmFactory {
static IVm Create(VmKind vmKind)
{
IVm result = null;
switch (vmKind)
{
case VmKind.Status:
result = new StatusViewModel { StatusId = -1, CreateDate = DateTime.Now };
break;
case VmKind.Mro:
result = new MroViewModel { StatusId = -1, CreateDate = DateTime.Now };
break;
default:
throw new ArgumentException("Unknown VM type");
//break;
}
return result;
}
}
#endregion
I didn't actually use the Factory here, but you could do it for easy VM creation.
I have tried a lot but all in vain.
I have written a LINQ code but not able to save changes in database.
It is giving no error neither it is updating record.
class Program
{
[Table(Name = "mainframe_replication")]
public class mainframe_replication
{
private string _REPL_GUID;
[Column(IsPrimaryKey = true, Storage = "_REPL_GUID")]
public string REPL_GUID
{
get { return this._REPL_GUID; }
set { this._REPL_GUID = value; }
}
private string _REPL_TYPE;
[Column(Storage = "_REPL_TYPE")]
public string REPL_TYPE
{
get { return this._REPL_TYPE; }
set { this._REPL_TYPE = value; }
}
private string _RPT_ID;
[Column(Storage = "_RPT_ID")]
public string RPT_ID
{
get { return this._RPT_ID; }
set { this._RPT_ID = value; }
}
private string _RPT_VERS;
[Column(Storage = "_RPT_VERS")]
public string RPT_VERS
{
get { return this._RPT_VERS; }
set { this._RPT_VERS = value; }
}
private string _RPT_BYTES;
[Column(Storage = "_RPT_BYTES")]
public string RPT_BYTES
{
get { return this._RPT_BYTES; }
set { this._RPT_BYTES = value; }
}
private string _REPL_DTM;
[Column(Storage = "_REPL_DTM")]
public string REPL_DTM
{
get { return this._REPL_DTM; }
set { this._REPL_DTM = value; }
}
private string _NOTIF_ID;
[Column(Storage = "_NOTIF_ID")]
public string NOTIF_ID
{
get { return this._NOTIF_ID; }
set { this._NOTIF_ID = value; }
}
}
public class MyPoco
{
public string ReportId { get; set; }
public string Reportversion { get; set; }
public string ReportBytes { get; set; }
public string ReportDate { get; set; }
public string NotifId { get; set; }
public string RecipAdd { get; set; }
}
public static string loglocation;
static void Main(string[] args)
{
try
{
using (DataClasses1DataContext db = new DataClasses1DataContext())
{
Table<NOTIF_RECIP> NOTIF_RECIP_alias = db.GetTable<NOTIF_RECIP>();
Table<NOTIF_SCHED> NOTIF_SCHED_alias = db.GetTable<NOTIF_SCHED>();
Table<mainframe_replication> mainframe_replication_alias = db.GetTable<mainframe_replication>();
var ids = NOTIF_SCHED_alias.Select(x => x.NOTIF_RPT_ID).ToArray();
foreach (string notif_sched_data in ids)
{
var repljoinmf = mainframe_replication_alias
.Join(NOTIF_RECIP_alias, mfr => mfr.RPT_ID, nr => nr.NOTIF_RECIP_ID, (mfr, nr)
=> new MyPoco { ReportId = mfr.RPT_ID, Reportversion = mfr.RPT_VERS, ReportBytes = mfr.RPT_BYTES.ToString(), ReportDate = mfr.REPL_DTM.ToString(), NotifId = mfr.NOTIF_ID, RecipAdd = nr.NOTIF_RECIP_ADDR });
foreach (var repljoinmf_data in repljoinmf)
{
repljoinmf_data.NotifId = "abc";
//DO STUFF
// repljoinmf_data.NotifId = "Changedxyz";
}
db.SubmitChanges();
}
}
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
}
}
}
It is not giving any error while submitting changes.
What I need to change?
Any suggestion will be helpful.
If you want to save your changes back to the original data source, you need to be working with the actual entities instead of projections of those entities. Since you are joining two tables, one option is to put those instances into an anonymous type and update them:
foreach (string notif_sched_data in ids)
{
var repljoinmf = mainframe_replication_alias
.Join(NOTIF_RECIP_alias,
mfr => mfr.RPT_ID,
nr => nr.NOTIF_RECIP_ID,
(mfr, nr) => new {mfr, nr});
foreach (var repljoinmf_data in repljoinmf)
{
//DO STUFF
repljoinmf_data.mfr.NotifId = "Changedxyz";
}
db.SubmitChanges();
In your previous question you were told that anonymous types cannot be uptated, but in this case you're modifying instances that are referenced by the anonymous type. So you're not updating the anonymous type itself, just the objects that the anonymous type references.
You are modifying the property of your MyPoco object. This is just a representation of your table. That's why the database is not updated.
You can send your MyPoco to your client. It will perform some changes. Then you can recreate the entity and copy the properties from the Poco object. Then, you need to attach the modified entity to your table and then save the changes.
If you modify directly the entity, there is no need to attach, since it will have kept the links to the database (assuming you do that with the same Databasecontext).
I am standing on a complex issue for me. I need update some models, but I like to work with a generic class not to rewrite some code individually.
I need to update data that have lists on their properties, with possible exclusion or inclusion of items on these lists, but these lists can be of any other class / type. My questions are commented on the code.
These models are unrealistic and a bit absurds but have similarities with my real models, note that the logic is reversed on these relationships during updates.
Thanks for all.
public class RedNotebook
{
[Key]
public int Id { get; set; }
public string PageTitle { get; set; }
public virtual ICollection<Signature> Signatures { get; set; }
}
public class BlueNotebook
{
[Key]
public int Id { get; set; }
public DateTime Entrance { get; set; }
public DateTime Leave { get; set; }
public virtual ICollection<Guest> GuestList { get; set; }
}
public class Signature
{
[key]
public int Id { get; set; }
public string PeopleSignature { get; set; }
public int IdRedNotebook { get; set; }
public int IdBlueNotebook { get; set; }
[ForeignKey("IdRedNotebook")]
public virtual RedNotebook { get; set; }
[ForeignKey("IdBlueNotebook")]
public virtual BlueNotebook { get; set; }
}
public class Guest
{
[key]
public int Id { get; set; }
public string Name { get; set; }
public int SeatNumber { get; set; }
public int IdBlueNotebook { get; set; }
[ForeignKey("IdBlueNotebook")]
public virtual BlueNotebook { get; set; }
}
/**********************/
public void UpdateData(T newData, out string msg)
{
try
{
var propId = newData.GetType().GetProperty("Id");
if (propId == null)
{
msg = "Unable to identify the identity of the reported data.";
return;
}
int id = Convert.ToInt32(propId.GetValue(newData));
if (id <= 0)
{
msg = "Unable to identify the identity of the reported data.";
return;
}
//instance a determined DbContext and Model<T>
var contexto = new CtxCliente(DAO.Classes.Util.InstanciarConexao(strCripto, (DAO.Conectores) Conector));
var model = contexto.Set<T>();
var targetData = model.Find(id);
if (targetData == null)
{
model.Add(newData);
contexto.Entry(model).State = EntityState.Added;
msg = "An addition was made because there was no previous reference.";
}
if (Convert.ToInt32(targetData.GetType().GetProperty("Id").GetValue(targetData)) > 0)
{
contexto.Entry(targetData).CurrentValues.SetValues(newData);
contexto.Entry(targetData).State = EntityState.Modified;
msg = string.Empty;
}
//TODO - 1) GET THE VIRTUAL PROPERTIES OF WHICH TYPE targetData ICollection
//TODO - 2) COMPARE THE CONTENT OF VIRTUAL PROPERTIES OF targetData WITH THE CONTENTS OF VIRTUAL PROPERTIES UPDATE, BOTH ICollection
//TODO - 3) REMOVE EXCESS OF targetData AND / OR ADD THAT AS THE CASE MAY BE MISSING (A - CLEAR DIFFERENCE, B - ADD DIFFERENCE)
//through the properties to identify those that are of the collection type
foreach (var propertytargetData in targetData.GetType().GetProperties())
{
if (!propertytargetData.PropertyType.IsGenericType)
continue;
var propsNewData = newData.GetType().GetProperty(propertytargetData.Name);
#region
//if all list items were removed on update
if (propsNewData == null && propertytargetData != null)
{
// NOT TESTED, MAYBE NOT WORK CORRECTLY
propertytargetData.SetValue(targetData,null);
}
//If an item was included or removed
else if (propsNewData != null)
{
var valTargetData = propertytargetData.GetValue(targetData);
var valNewData = propsNewData.GetValue(newData);
var listItemsTargetData = (IEnumerable) valTargetData;
var listItemsNewData = (IEnumerable) valNewData;
int countItemsTargetData = listItemsTargetData.Cast<object>().Count();
int countItemsNewData = listItemsNewData.Cast<object>().Count();
if (countItemsTargetData > countItemsNewData) //remove discarded
{
foreach (var itemtargetData in listItemsTargetData)
{
var idItemtargetData = itemtargetData.GetType().GetProperty("Id").GetValue(itemtargetData);
var existing = (from object itemListNewData in listItemsNewData
select itemListNewData.GetType().GetProperty("Id").GetValue(itemListNewData))
.Any(iditemListNewData => (int) idItemtargetData == (int) iditemListNewData);
if (!existing) //remove
{
//how to remove from the list?????? (targetData)
}
else //update
{
foreach (var itemListNewData in listItemsNewData)
{
var props = itemListNewData.GetType().GetProperties();
foreach (var propertyInfo in props)
{
foreach (var item in listItemsTargetData)
{
var p = item.GetType().GetProperty(propertyInfo.Name);
if (p != null && !p.PropertyType.IsGenericType)
{
p.SetValue(item, propertyInfo.GetValue(itemListNewData));
}
}
}
}
}
}
}
else if (countItemsTargetData < countItemsNewData) //Items need to be included
{
foreach (var newItem in listItemsNewData)
{
var idnewItem = newItem.GetType().GetProperty("Id").GetValue(newItem);
if ((int) idnewItem == 0)
{
//how to insert in list???????? (targetData)
}
else // remove and/or update some before (reduntant!?)
{
foreach (var itemtargetData in listItemsTargetData)
{
var idItemtargetData = itemtargetData.GetType().GetProperty("Id").GetValue(itemtargetData);
var existing = (from object itemListNewData in listItemsNewData
select itemListNewData.GetType().GetProperty("Id").GetValue(itemListNewData))
.Any(iditemListNewData => (int)idItemtargetData == (int)iditemListNewData);
if (!existing) //remove
{
//how to remove from the list?????? (targetData)
}
else //update
{
foreach (var itemListNewData in listItemsNewData)
{
var props = itemListNewData.GetType().GetProperties();
foreach (var propertyInfo in props)
{
foreach (var item in listItemsTargetData)
{
var p = item.GetType().GetProperty(propertyInfo.Name);
if (p != null && !p.PropertyType.IsGenericType)
{
p.SetValue(item, propertyInfo.GetValue(itemListNewData));
}
}
}
}
}
}
}
}
}
}
}
contexto.SaveChanges(); //save data on model
}
catch(...){}
}
Haven't tested it . But it should work if both source and dest implement the same ICollection interface and T has an Id property of type System.Int32. It uses the new dynamic keyword that enables you to do duck typing ;
private class IdComparer : IEqualityComparer<object>
{
public bool Equals(object x, object y)
{
//return ((dynamic) x).Id = ((dynamic) y).Id; //previous with convertion error
return ((dynamic) x).Id == ((dynamic) y).Id;
}
public int GetHashCode(object obj)
{
return ((dynamic) obj).Id;
}
}
private static void Copy(IEnumerable source, IEnumerable dest)
{
var cmp = new IdComparer();
var toRemove = dest.Cast<object>().Except(source.Cast<object>(),cmp).ToList();
var toAdd= source.Cast<object>().Except(dest.Cast<object>(),cmp).ToList();
foreach(var item in toAdd)
{
// dynamic runtime tries to find method that matches signiture void Add(T value so we add dummy variable so that it knows to search for bool Add(T value)
var dummy= ((dynamic) dest).Add(item);
}
foreach (var item in toRemove)
{
var dummy= ((dynamic)dest).Remove(item);
}
}