Is it possible to overload the NHibernate created lazy collection? - c#

Basically, i have this class:
public class Promotion : Entity<int>
{
public Promotion()
{
Created = DateTime.Now;
this.files = new HashedSet<PromoImage>();
}
public Promotion(int id, string Name, DateTime created, DateTime from, DateTime to, ISet<PromoImage> files)
{
this.Id = id;
this.Created = created;
this.From = from;
this.To = to;
this.files = files;
}
public Promotion(string Name, DateTime created,DateTime from, DateTime to, ISet<PromoImage> files)
{
this.Created = created;
this.From = from;
this.To = to;
this.files = files;
}
public virtual DateTime Created { get; protected set; }
public virtual string Name { get; protected set; }
public virtual DateTime? From { get; protected set; }
public virtual DateTime? To { get; protected set; }
private ISet<PromoImage> files;
public IEnumerable<PromoImage> Files
{
get
{
return files;
}
}
public virtual bool AddPromoImage(PromoImage newPromoImage)
{
if (newPromoImage != null && files.Add(newPromoImage))
{
return true;
}
return false;
}
public virtual bool RemovePromoImage(PromoImage promoImage)
{
if (promoImage != null && files.Remove(promoImage))
{
return true;
}
return false;
}
public virtual bool ChangePhotoPositions(IEnumerable<Row> rows)
{
foreach (var row in rows)
{
List<PromoImage> photos = Files.Where(p => row.PhotoIdAndPosition.Any(q => q.First == p.Id)).ToList();
int totalWidth = 0;
int lastHeight = photos[0].Image.Height;
bool allPhotosHaveSameHeight = true;
foreach (var photo in photos)
{
allPhotosHaveSameHeight = lastHeight == photo.Image.Height;
totalWidth += photo.Image.Width;
lastHeight = photo.Image.Height;
}
if (totalWidth > 734 && !allPhotosHaveSameHeight)
{
return false;
}
else
{
foreach (var photo in photos)
{
var newPosition = row.PhotoIdAndPosition.Single(p => p.First == photo.Id).Second;
photo.Row = row.Level;
photo.Position = newPosition;
}
}
}
return true;
}
public virtual bool SetPromotionDateRange(DateTime from, DateTime to)
{
if (from > DateTime.Now)
{
if (from > to)
{
From = from;
To = to;
return true;
}
else
{
return false;
}
}
else
return false;
}
}
and this class which stores any changes to the Promotion object as persistent events:
public class StagedPromotion:Promotion
{
public StagedPromotion():base()
{
Changes = new HashedSet<Change>();
}
public virtual DateTime? CommitedWhen { get; protected set; }
public virtual ISet<Change> Changes { get; protected set; }
public virtual Promotion WorkingPromotion
{
get
{
Promotion promotion = new Promotion(this.Name, this.Created, this.From.Value, this.To.Value, this.files);
foreach (var change in Changes)
{
change.ExecuteChange(promotion);
}
return promotion;
}
}
public virtual bool ChangePhotoPositions(IEnumerable<Row> rows)
{
var promo = WorkingPromotion;
if (promo.ChangePhotoPositions(rows))
{
Change change = new ChangeRowAndPosition(new HashedSet<Row>(rows.ToList()));
Changes.Add(change);
return true;
}
else
return false;
}
public virtual bool SetPromotionDateRange(DateTime from, DateTime to)
{
var promo = WorkingPromotion;
if (promo.SetPromotionDateRange(from, to))
{
Change change = new ChangePromotionDate(from, to);
Changes.Add(change);
return true;
}
else
return false;
}
public virtual bool AddPromoImage(PromoImage newPromoImage)
{
//todo
}
public virtual bool RemovePromoImage(PromoImage promoImage)
{
//todo
}
public virtual IEnumerable<PromoImage> Files
{
get
{
var files = base.Files;
//replace each PromoImage object with a StagedPromoImage Decorator
//which registers any changes to the PromoImage as persistent events
}
}
}
Is it possible for NHibernate to return through the base.Files the lazily loaded collection and leave the StagedPromotion's Files property intact?
The idea is to create a versioning system, where each i can keep track of commited/uncommited Promotions.

you refer to base.Files as something that nhibernate would manage, but there is no way to map it as it stands. in order for nhibernate to provide a lazy collection the property would have to be virtual anyway so you couldn't have your staged promotions Files property without overriding the base property.

Related

Error on Edit Object in entityframework An entity object cannot be referenced by multiple instances of Entity ChangeTracker

I get an error when I call this text in my project. An entity object cannot be referenced by multiple instances of Entity Change Tracker.
public static short Udf_Edit_Invoice(tblInvoice inv)
{
using (DbContextModel db = new DbContextModel())
{
using (DbContextTransaction transaction = db.Database.BeginTransaction())
{
try
{
if (db.Entry<tblInvoice>(inv).State == EntityState.Detached)
{
db.Set<tblInvoice>().Attach(inv);
}
db.Entry<tblInvoice>(inv).State = System.Data.Entity.EntityState.Modified;
db.SaveChanges();
transaction.Commit();
return 1;
}
catch(Exception ee)
{
transaction.Rollback();
return 0;
}
}
}
}
Click Event: (And you can see the click event as follows, which was called inside a form and the information of the main class and the child class is poured into it, and then the editing command is called, which I encounter an error.)
tblInvoice invtPub = new tblInvoice();
private void Btn_Save_Click(object sender, EventArgs e)
{
if (TxtTax.Text == "")
{
TxtTax.Text = "0";
}
Decimal Dec_TotalPrice = 0;
Decimal Dec_TotalTakhfif = 0;
Entities.Entity.tblInvoiceDetail invdObj;
List<Entities.Entity.tblInvoiceDetail> invd = new List<Entities.Entity.tblInvoiceDetail>();
foreach (DataRow Row in FactDt.Rows)
{
invdObj = new Entities.Entity.tblInvoiceDetail();
Dec_TotalPrice += Decimal.Parse(Row["FoodTotalPrice"].ToString());
Dec_TotalTakhfif += Decimal.Parse(Row["FoodDiscount"].ToString());
invdObj.AssetID = int.Parse(Row["FoodID"].ToString());
invdObj.AssetQuantity = Double.Parse(Row["FoodQuantity"].ToString());
invdObj.Discount = Double.Parse(Row["FoodDiscount"].ToString());
invdObj.UnitPrice = Double.Parse(Row["FoodUnitPrice"].ToString());
invdObj.InvoiceID = 0;
invdObj.TotalPrice = Double.Parse(Row["FoodTotalPrice"].ToString());
invd.Add(invdObj);
}
invtPub.CreatorUserID = BLL.Class_GlobalVars.ThisPUserID;
invtPub.CustomerID = long.Parse(Txt_CustomerCode.Text.Trim());
invtPub.DateOfFactor = DateTime.Now;
invtPub.InvoiceTitleID = 0;
invtPub.IsCanceled = false;
invtPub.PayCardOfInvoice = Decimal.Parse(Txt_Card.Text.Trim());
invtPub.PayCashOfInvoice = Decimal.Parse(Txt_Cach.Text.Trim());
invtPub.PayCredOfInvoice = (Decimal.Parse(Txt_Debit.Text.Trim()) < 0 ? Decimal.Parse(Txt_Debit.Text.Trim()) : 0);
invtPub.PayDebtOfInvoice = (Decimal.Parse(Txt_Debit.Text.Trim()) >= 0 ? Decimal.Parse(Txt_Debit.Text.Trim()) : 0);
invtPub.ShamsiDateOfFactor = ShamsiTools.Class_SHamsiTools.UDF_MiladiDateToShmasi(DateTime.Now);
invtPub.StrDescription = Txt_Description.Text.Trim();
invtPub.TaxOfInvoice = Decimal.Parse(TxtTax.Text.Trim());
invtPub.TotalPriceOfFactor = Dec_TotalPrice;
invtPub.TotalPriceOfFactorWithTax = (Decimal.Parse(TxtTax.Text) + Dec_TotalPrice);
invtPub.tblInvoiceDetail = invd;
if (Entities.Classes.Class_tblInvoice.Udf_Edit_Invoice(invtPub) == 1)
{
Lbl_Total_Aglam.Text = "0";
Lbl_Total_Price.Text = "0";
Lbl_Total_Takhfif.Text = "0";
GrPr_Mohasebeh.Enabled = false;
Grpr_Custumer.Enabled = true;
Grpr_Factor.Enabled = true;
FactDt.Rows.Clear();
for (int i = FactDt.Rows.Count - 1; i >= 0; i--)
{
DataRow dr = FactDt.Rows[i];
{
dr.Delete();
}
}
Grd_Factor.DataSource = FactDt;
MessageBox.Show("Edited . . . . . .");
UDF_TXTFactorClear();
UDF_TXTCustomerClear();
}
else
{
}
}
and total error message:
An entity object cannot be referenced by multiple instances of IEntityChangeTracker.
"EntityFramework"
at System.Data.Entity.Core.Objects.ObjectContext.VerifyContextForAddOrAttach(IEntityWrapper wrappedEntity)
at System.Data.Entity.Core.Objects.ObjectContext.AttachSingleObject(IEntityWrapper wrappedEntity, EntitySet entitySet)
at System.Data.Entity.Core.Objects.ObjectContext.AttachTo(String entitySetName, Object entity)
at System.Data.Entity.Internal.Linq.InternalSet`1.<>c__DisplayClassa.<Attach>b__9()
at System.Data.Entity.Internal.Linq.InternalSet`1.ActOnSet(Action action, EntityState newState, Object entity, String methodName)
at System.Data.Entity.Internal.Linq.InternalSet`1.Attach(Object entity)
at System.Data.Entity.Internal.InternalEntityEntry.set_State(EntityState value)
at System.Data.Entity.Infrastructure.DbEntityEntry`1.set_State(EntityState value)
at Entities.Classes.Class_tblInvoice.Udf_Edit_Invoice(tblInvoice inv) in E:\Programming\P1\ADVD9\TarkeEtiad\SourceCodeEF\Entities\Classes\Class_tblInvoice.cs:line 77
This: tblInvoice invtPub = new tblInvoice();
[Table("tblInvoice")]
public partial class tblInvoice
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public tblInvoice()
{
tblInvoiceDetail = new HashSet<tblInvoiceDetail>();
}
[Key]
public long InvoiceTitleID { get; set; }
[Required]
public Boolean IsCanceled { get; set; }
[Required]
public int CreatorUserID { get; set; }
[Required]
public long CustomerID { get; set; }
public DateTime DateOfFactor { get; set; }
[Required]
[StringLength(10)]
public string ShamsiDateOfFactor { get; set; }
public decimal? TotalPriceOfFactor { get; set; }
public decimal TaxOfInvoice { get; set; }
public decimal TotalPriceOfFactorWithTax { get; set; }
public decimal PayCashOfInvoice { get; set; } //نقدی
public decimal PayCardOfInvoice { get; set; } //کارتخوان
public decimal PayDebtOfInvoice { get; set; } //بدهی
public decimal PayCredOfInvoice { get; set; } //طلب
[MaxLength(1500)]
public String StrDescription { get; set; }
public virtual tblCustomers tblCustomers { get; set; }
public virtual tblPUsers tblPUsers { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<tblInvoiceDetail> tblInvoiceDetail { get; set; }
}
Don't use any transactions since you only need to add one record.
try this
using (DbContextModel db = new DbContextModel())
{
try
{
var existedInvoice= db.Set<tblInvoice>().FirstOrDefault(i=> i.InvoiceTitleID = inv.InvoiceTitleID );
if (existedInvoice!=null)
{
db.Entry(existedInvoice).CurrentValues.SetValues(inv);
db.SaveChanges();
return 1;
} else return 0;
}
catch(Exception ee)
{
return 0;
}
}

Turning a IEnumerable into a ObservableCollection

I have this IEnumerable
public static IEnumerable<Shipments> Shipments
{
get
{
var hash = new HashSet<Routes>(new ShipmentsComparer()); // rename ShipmentsComparer cause it is actually RoutesComparer
foreach (var item in Loads)
{
if (item.ShipTo.Contains(" "))
{
foreach (var item2 in Routes.Where(d => d.DockCode == item.ShipTo.Substring(0, item.ShipTo.IndexOf(" ")) && d.CarrierDeparture.TimeOfDay == item.ShipTime.TimeOfDay))
{
if (hash.Add(item2))
{
yield return new Shipments { Arrival = item2.CarrierArrival, Departure = item2.CarrierDeparture, Issuer = item.Customer, Destination = item.ShipTo, LoadType = item.LoadType };
}
}
}
}
}
}
My goal is to add
yield return new Shipments { Arrival = item2.CarrierArrival, Departure = item2.CarrierDeparture, Issuer = item.Customer, Destination = item.ShipTo, LoadType = item.LoadType };
this into a ObservableCollection
At the moment I am using this IEnumerable to populate a DataGrid I am unable to keep using it this way as when the datagrid refreshes it removes the sorting that the user has selected.
EDIT:
Class Shipments
public class Shipments : BaseClass
{
private DateTime _Arrival;
public DateTime Arrival
{
get { return _Arrival; }
set { _Arrival = value; RaisePropertyChanged(nameof(Arrival)); }
}
private DateTime _Departure;
public DateTime Departure
{
get { return _Departure; }
set { _Departure = value; RaisePropertyChanged(nameof(Departure)); }
}
private string _Issuer;
public string Issuer
{
get { return _Issuer; }
set { _Issuer = value; RaisePropertyChanged(nameof(Issuer)); }
}
private string _Destination;
public string Destination
{
get { return _Destination; }
set { _Destination = value; RaisePropertyChanged(nameof(Destination)); }
}
private string _LoadType;
public string LoadType
{
get
{
_LoadType = (Departure - Arrival).ToString();
if (_LoadType == "00:30:00")
{
_LoadType = "Drop Hook";
}
else
{
_LoadType = "Live Load";
}
return _LoadType.ToString();
}
set
{
_LoadType = value;
}
}
Comparer
class ShipmentsComparer : IEqualityComparer<Routes>
{
public bool Equals(Routes route1, Routes route2) =>
route1.DockCode == route2.DockCode && route1.CarrierArrival.Date == route2.CarrierArrival.Date &&
route1.CarrierArrival.Hour == route2.CarrierArrival.Hour && route1.CarrierArrival.Minute == route2.CarrierArrival.Minute;
public int GetHashCode(Routes obj) =>
obj.DockCode.GetHashCode() ^ (obj.CarrierArrival.GetHashCode() * 13);
}
The routes are added from a PDF file into a ObservableCollection Loads are added from a SQL statement into their own ObservableCollection
public static ObservableCollection<Routes> Routes { get; set; } = new ObservableCollection<Routes>();
public static ObservableCollection<Loads> Loads { get; set; } = new ObservableCollection<Loads>();
Currently I use the IEnumerable Above
I would like it to be a ObservableCollection instead of a IEnumerable for easy refreshing of the DataGrid and keep items filtered.
I looked at the edits you made to the topic question.
But I still do not understand many details of the question, so I cannot offer an implementation that is completely suitable for the answer:
Loads what type are the elements?
What is considered a change to an existing Shipments and what is a new Shipments instance?
The very unfortunate name of the Shipments collection property is the same as the item type name of this collection.
Here is an option, to the best of my understanding of your task.
But I cannot guarantee that it will suit you completely.
ShipmentsComparer
public class ShipmentsComparer : IEqualityComparer<Shipments>
{
public bool Equals(Shipments left, Shipments right)
{
if (left == null) return right == null;
return left.Arrival == right.Arrival &&
left.Departure == right.Departure &&
left.Issuer == right.Issuer &&
left.Destination == right.Destination &&
left.LoadType == right.LoadType;
}
public int GetHashCode(Shipments shipments)
{
int hashCode = 376624599;
hashCode = hashCode * -1521134295 + shipments.Arrival.GetHashCode();
hashCode = hashCode * -1521134295 + shipments.Departure.GetHashCode();
hashCode = hashCode * -1521134295 + EqualityComparer<string>.Default.GetHashCode(shipments.Issuer);
hashCode = hashCode * -1521134295 + EqualityComparer<string>.Default.GetHashCode(shipments.Destination);
hashCode = hashCode * -1521134295 + EqualityComparer<string>.Default.GetHashCode(shipments.LoadType);
return hashCode;
}
}
RoutesComparer
public class RoutesComparer : IEqualityComparer<Routes>
{
public bool Equals(Routes route1, Routes route2) =>
route1.DockCode == route2.DockCode && route1.CarrierArrival.Date == route2.CarrierArrival.Date &&
route1.CarrierArrival.Hour == route2.CarrierArrival.Hour && route1.CarrierArrival.Minute == route2.CarrierArrival.Minute;
public int GetHashCode(Routes obj) =>
obj.DockCode.GetHashCode() ^ (obj.CarrierArrival.GetHashCode() * 13);
}
public class Routes
{
public object DockCode { get; internal set; }
public DateTime CarrierArrival { get; internal set; }
}
Filtered
public ObservableCollection<Routes> Loads { get; } = new ObservableCollection<Routes>();
public ObservableCollection<Routes> Routes { get; } = new ObservableCollection<Routes>();
public ObservableCollection<Shipments> ShipmentsColl { get; } = new ObservableCollection<Shipments>();
public void RenderRoutes()
{
var hashRoutes = new HashSet<Routes>(new RoutesComparer());
HashSet<Shipments> hashShipments = new HashSet<Shipments>(new ShipmentsComparer());
foreach (var item in Loads)
{
if (item.ShipTo.Contains(" "))
{
foreach (var item2 in Routes.Where(d => d.DockCode == item.ShipTo.Substring(0, item.ShipTo.IndexOf(" ")) && d.CarrierDeparture.TimeOfDay == item.ShipTime.TimeOfDay))
{
if (hashRoutes.Add(item2))
{
// Forming a collection of results
hashShipments.Add( new Shipments { Arrival = item2.CarrierArrival, Departure = item2.CarrierDeparture, Issuer = item.Customer, Destination = item.ShipTo, LoadType = item.LoadType });
}
}
}
}
// We bring in line the public collection of the resulting collection of results.
for (int i = ShipmentsColl.Count-1; i >=0; i--)
{
if (hashShipments.Contains(ShipmentsColl[i]))
hashShipments.Remove(ShipmentsColl[i]);
else
ShipmentsColl.RemoveAt(i);
}
// Add missing results.
foreach (Shipments shipments in hashShipments)
{
ShipmentsColl.Add(shipments);
}
}
Implementation for a simple example from the topic Combining matching values in 2 observablecollections into a 3rd [WPF]
Class Person
public class Person : IEquatable<Person>
{
public string FirstName { get; }
public string LastName { get; }
public Person(string firstName, string lastName)
{
FirstName = firstName;
LastName = lastName;
}
public override bool Equals(object obj)
{
return Equals(obj as Person);
}
public bool Equals(Person other)
{
return other != null &&
FirstName == other.FirstName &&
LastName == other.LastName;
}
public override int GetHashCode()
{
int hashCode = 1938039292;
hashCode = hashCode * -1521134295 + EqualityComparer<string>.Default.GetHashCode(FirstName);
hashCode = hashCode * -1521134295 + EqualityComparer<string>.Default.GetHashCode(LastName);
return hashCode;
}
}
Filtered
public ObservableCollection<Person> People1 { get; } = new ObservableCollection<Person>()
{
new Person("Donald", "Duck"),
new Person("Daisy", "Duck"),
new Person("Jack", "Daniels")
};
public ObservableCollection<Person> People2 { get; } = new ObservableCollection<Person>()
{
new Person("Donald", "Duck"),
new Person("Daisy", "Duck"),
new Person("Jim", "Beam")
};
public ObservableCollection<Person> PeopleInBothCollections { get; } = new ObservableCollection<Person>();
public void RenderPeople()
{
List<Person> people = People1.Where(prs => People2.Contains(prs))
.ToList();
for (int i = PeopleInBothCollections.Count-1; i >=0 ; i--)
{
if (!people.Contains(PeopleInBothCollections[i]))
PeopleInBothCollections.RemoveAt(i);
}
foreach (Person person in people)
{
if (!PeopleInBothCollections.Contains(person))
PeopleInBothCollections.Add(person);
}
}

The advice on the simplification of the functions

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();
}
}

complex way for update generic data model with reflection and collections

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);
}
}

Serialization woes

I have an issue with serialization. I understand that methods can not be serialized for good reason, so I created a factory class to convert my existing class into a more manageable class.
This is the original class:
using Assets.Components;
using Assets.Data;
using IO.Components;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Web;
namespace Assets
{
[Serializable]
public class Asset
{
#region Fields
Metadata _metadata;
string _fileName;
string _companyId;
#endregion
#region Properties
[Required]
public string DisplayName { get; set; }
public string Description { get; set; }
public string Tags { get; set; }
public int Id { get; set; }
public int CategoryId { get; set; }
public AssetType Type { get; set; }
public int LanguageId { get; set; }
public int StatusId { get; set; }
public DateTime DateCreated { get; set; }
public long DateCreatedMilliseconds { get { return DateCreated.ToJavaScriptMilliseconds(); } }
public int Views { get; set; }
public int Downloads { get; set; }
public string ThumbNail { get; set; }
public string Filename
{
set { _fileName = value; }
}
[Required]
public string CompanyId
{
set { _companyId = value; }
}
public string GetBaseDirectory
{
get { return "/Public/Uploads/" + this._companyId + "/0"; }
}
public double Rating
{
get
{
List<int> Score = new List<int>();
foreach (IRating oRating in this.Ratings())
{
Score.Add(oRating.Score);
}
return (Score.Count > 0) ? Score.Average() : 0;
}
}
public Metadata Metadata
{
get
{
if (_metadata == null)
{
_metadata = new Metadata(this.Id);
if (_metadata.AssetId == 0)
{
try
{
if (GetFilename() != null)
{
string path = System.IO.Path.Combine(HttpContext.Current.Server.MapPath(this.GetBaseDirectory), GetFilename());
if (!System.IO.File.Exists(path))
_metadata = new Metadata();
else
{
_metadata = MetadataExtractor.Create(path, this.Id);
_metadata.save();
}
}
else
{
_metadata = new Metadata();
}
}
catch
{
_metadata = new Metadata();
}
}
}
return _metadata;
}
}
public bool IsConverted { get; set; }
public string UserId { get; set; }
public DateTime DateModified { get; set; }
public long DateModifiedMilliseconds { get { return DateCreated.ToJavaScriptMilliseconds(); } }
public string Culture { get; set; }
public string Language { get; set; }
public string UserName { get; set; }
public string Email { get; set; }
public int CategoryCount { get; set; }
public int AssetCount { get; set; }
public bool IgnoreRights { get; set; }
#endregion
#region Contructors
/// <summary>
/// Default constructor
/// </summary>
public Asset()
{
}
/// <summary>
/// Get's the asset from the database, but set's the status to the profiles Requires Approval state.
/// </summary>
/// <param name="Id">Asset Id</param>
/// <param name="IsViewing">Boolean to update the reports table</param>
/// <param name="IsDownloading">Boolean to update the reports table</param>
public Asset(int Id, string UserId, string CompanyId, bool IsViewing, bool IsDownloading)
{
try
{
Asset oAsset = AssetData.GetAsset(Id, IsViewing, IsDownloading, UserId, CompanyId);
// Assign the values to this class
this.Id = oAsset.Id;
this.DisplayName = oAsset.DisplayName;
this.IsConverted = oAsset.IsConverted;
this.StatusId = oAsset.StatusId;
this.Type = oAsset.Type;
this.UserId = oAsset.UserId;
this.UserName = oAsset.UserName;
this.CompanyId = oAsset.GetCompanyId();
this.Description = oAsset.Description;
this.Tags = oAsset.Tags;
this.LanguageId = oAsset.LanguageId;
this.Culture = oAsset.Culture;
this.Language = oAsset.Language;
if (oAsset.ThumbNail != null) this.ThumbNail = oAsset.ThumbNail;
this.Filename = oAsset.GetFilename();
if (oAsset.Views != 0) this.Views = oAsset.Views;
if (oAsset.Downloads != 0) this.Downloads = oAsset.Downloads;
}
catch (Exception ex)
{
Stars.BLL.Error.Handling.LogError("Skipstone", "Asset", "Asset", ex.Message, ex.ToString()); // Record our error
}
}
/// <summary>
/// Used for executing some of the public methods
/// </summary>
/// <param name="Id">Id of the asset to retrieve</param>
/// <param name="CompanyId">The CompanyId of the company for the User</param>
public Asset(int Id, string CompanyId)
{
this.Id = Id;
this.CompanyId = CompanyId;
}
#endregion
#region Public methods
public string GetCompanyId()
{
return _companyId;
}
public string GetFilename()
{
return _fileName;
}
public string GetThumbnail()
{
return this.GetBaseDirectory + "/" + this.ThumbNail;
}
public string GetSmallThumbnail()
{
return this.GetBaseDirectory + "/sml_" + this.ThumbNail;
}
public Collection<IRating> Ratings()
{
Collection<IRating> oRatings = new Collection<IRating>();
try
{
oRatings = RatingData.get(this.Id);
}
catch
{
// record our error
}
return oRatings;
}
public Collection<IComment> Comments()
{
Collection<IComment> oComments = new Collection<IComment>();
try
{
oComments = CommentData.getAssetComments(this.Id);
}
catch (Exception ex)
{
// record our error
}
return oComments;
}
public void SaveMetadata()
{
}
public Collection<GenericType> Categories()
{
return MiscellaneousManager.AssetCategories(this.Id, GetCompanyId());
}
public void Save()
{
if (this.Id > 0)
{
AssetData.update(this);
}
else
{
Asset oAsset = AssetData.create(this);
this.Id = oAsset.Id;
this.DisplayName = oAsset.DisplayName;
this.Type = oAsset.Type;
this.UserId = oAsset.UserId;
this.CompanyId = oAsset.GetCompanyId();
this.Description = oAsset.Description;
this.Tags = oAsset.Tags;
this.LanguageId = oAsset.LanguageId;
this.Culture = oAsset.Culture;
this.Language = oAsset.Language;
if (oAsset.ThumbNail != null) this.ThumbNail = oAsset.ThumbNail;
this.Filename = oAsset.GetFilename();
if (oAsset.Views != 0) this.Views = oAsset.Views;
if (oAsset.Downloads != 0) this.Downloads = oAsset.Downloads;
}
}
public void delete()
{
AssetData.delete(this.Id);
AssetManager.RemoveFromCache(this);
}
#endregion
}
}
and this is my factory method:
private static SerialisedAsset AssetFactory(Assets.Asset Object)
{
SerialisedAsset FactoryObject = new SerialisedAsset()
{
Id = Object.Id,
Name = Object.DisplayName,
UserId = Object.UserId,
UserName = Object.UserName,
CompanyId = Object.GetCompanyId(),
Description = Object.Description,
Tags = Object.Tags,
DateCreated = Object.DateCreated,
Path = Object.GetBaseDirectory,
FileName = Object.GetFilename(),
ThumbnailName = Object.ThumbNail
};
return FactoryObject;
}
which is part of my audittrailmanager class:
using Assets;
using Core;
using Reports.Objects;
using System;
using System.IO;
using System.Xml.Serialization;
namespace Reports.Components
{
public static class AuditTrailManager
{
#region Public methods
public static Audit AuditTrailFactory(Profile Profile, Object Object, Event Event)
{
Audit Audit = new Audit(SerializeObject(Object))
{
UserId = Profile.UserId,
UserName = Profile.UserName,
CompanyId = Profile.CompanyId,
ObjectName = GetObjectNameFromType(Object.GetType().ToString()),
Event = Event
};
return Audit;
}
#endregion
#region Private methods
private static string GetObjectNameFromType(string Type)
{
switch (Type)
{
case "Assets.Asset": return "Asset";
case "Core.SiteSetting": return "CompanySettings";
}
return "";
}
private static string SerializeObject(Object Object)
{
string ObjectType = Object.GetType().ToString();
switch (ObjectType)
{
case "Assets.Asset": return Serialize(AssetFactory((Asset)Object));
}
return ""; // If we fail
}
private static string Serialize(Object Object)
{
XmlSerializer ser = new XmlSerializer(Object.GetType());
using (StringWriter Xml = new StringWriter())
{
ser.Serialize(Xml, Object);
return (Xml.ToString());
}
}
private static SerialisedAsset AssetFactory(Assets.Asset Object)
{
SerialisedAsset FactoryObject = new SerialisedAsset()
{
Id = Object.Id,
Name = Object.DisplayName,
UserId = Object.UserId,
UserName = Object.UserName,
CompanyId = Object.GetCompanyId(),
Description = Object.Description,
Tags = Object.Tags,
DateCreated = Object.DateCreated,
Path = Object.GetBaseDirectory,
FileName = Object.GetFilename(),
ThumbnailName = Object.ThumbNail
};
return FactoryObject;
}
#endregion
}
}
What I am trying to do is create an audit trail which records the object I am working on (in this case an asset) and I am serializing the class and inserting it into the database for use in reporting, etc.
My question is; is this the way to do it. Is there a better way?

Categories

Resources