I have the following interface:
public delegate void NotifyOnModulesAvailabilityHandler(Lazy[] modules);
public interface IModulesLoader
{
event NotifyOnModulesAvailabilityHandler NotifyOnModulesAvailability;
Lazy<UserControl, IModuleMetadata>[] Modules { get; set; }
void OnImportsSatisfied();
}
I'm tring to implement this interface like this:
public class ModulesLoader : IModulesLoader, IPartImportsSatisfiedNotification
{
#region Events
public event NotifyOnModulesAvailabilityHandler NotifyOnModulesAvailability;
#endregion
#region Public Contructor
public ModulesLoader()
{
DeploymentCatalogService.Instance.Initialize();
CompositionInitializer.SatisfyImports(this);
this.LoadModules();
}
#endregion
#region Properties
[ImportMany(AllowRecomposition = true)]
public Lazy<UserControl, IModuleMetadata>[] Modules
{
get;
set;
}
#endregion
#region IPartImportsSatisfiedNotification Members
public void OnImportsSatisfied()
{
var handler = this.NotifyOnModulesAvailability;
if (handler != null)
{
handler(this.Modules);
}
}
#endregion
#region Private Methods
private void LoadModules()
{
var wc = new WebClient();
wc.OpenReadCompleted += (s, e) =>
{
var streamInfo = e.Result;
var xElement = XElement.Load(streamInfo);
var modulesList = from m in xElement.Elements("ModuleInfo")
select m;
if (modulesList.Any())
{
foreach (var module in modulesList)
{
var moduleName = module.Attribute("XapFilename").Value;
DeploymentCatalogService.Instance.AddXap(moduleName);
}
}
};
wc.OpenReadAsync(new Uri("ModulesCatalog.xml", UriKind.Relative));
}
#endregion
}
I get the following error:
Error 1 'TunisiaMeeting.Extensibility.Shell.Helpers.Deployment.ModulesLoader' does not implement interface member 'TunisiaMeeting.MefBase.Interfaces.IModulesLoader.Modules'. 'TunisiaMeeting.Extensibility.Shell.Helpers.Deployment.ModulesLoader.Modules' cannot implement 'TunisiaMeeting.MefBase.Interfaces.IModulesLoader.Modules' because it does not have the matching return type of 'System.Lazy``2<System.Windows.Controls.UserControl,TunisiaMeeting.MefBase.Interfaces.IModuleMetadata>[]'. C:\Imed\TunisiaMeeting\TunisiaMeeting.Extensibility.Shell\Helpers\Deployment\ModulesLoader.cs 18 18 TunisiaMeeting.Extensibility.Shell
I'm pretty sure I have the same return Type Lazy<UserControl, IModuleMetadata>[] in both my class and my interface for my property.
Any Help please ?
Thank you all
You haven't shown where UserControl and IModuleMetadata come from... my guess is that your interface is referring to one pair of types whereas your implementation is referring to a different pair:
Make sure they're referring to the same types in the same namespaces
Make sure you only have one copy of each type (e.g. that you haven't got one copy in a class library, and redeclared it somewhere else)
Related
I need to build a processing screen for customer locations that determines and then updates the residential flag on locations.
This code correctly processes each selected record and appears to update the appropriate fields. But the problem I am encountering is that my changes to Location are not being saved back to the database.
The Customer Locations graph requires the business account to be specified before you can enter a Location ID, and I suspect that because of that I cannot simply update the Locations view on the graph. But I cannot find any documentation or code examples indicating what approach I should use here.
Here is the code on my processing screen graph:
public class ProcessCustomerLocations : PXGraph<ProcessCustomerLocations>
{
public PXCancel<Location> Cancel;
public PXProcessing<Location, Where<Location.isActive, Equal<True>>> Locations;
public static void Process(List<Location> locations)
{
var graph = PXGraph.CreateInstance<CustomerLocationMaint>();
CustomerLocationMaint_Extension graphExt = graph.GetExtension<CustomerLocationMaint_Extension>();
foreach (var location in locations)
{
graphExt.UpdateLocation(location, true);
}
}
public ProcessCustomerLocations()
{
Locations.SetProcessDelegate(Process);
}
}
And here is my code on the CustomerLocationMaint_Extension graph:
public class CustomerLocationMaint_Extension : PXGraphExtension<CustomerLocationMaint>
{
public void UpdateLocation(Location location, bool isMassProcess = false)
{
bool isRes = false;
Base.Location.Current = Base.Location.Search<Location.locationID>(location.LocationID, location.BAccountID);
LocationExt locationExt = location.GetExtension<LocationExt>();
// INSERT CODE TO DETERMINE VALUE OF isRes
locationExt.UsrResidentialValidated = true;
location.CResedential = isRes;
Base.Location.Update(location);
Base.Actions.PressSave();
}
}
One of the fields I am updating on Location is a custom field called UsrResidentialValidated. Here is the code for that field.
namespace PX.Objects.CR
{
public class LocationExt : PXCacheExtension<PX.Objects.CR.Location>
{
#region UsrResidentialValidated
[PXDBBool]
[PXUIField(DisplayName="Residential Validated")]
public virtual bool? UsrResidentialValidated { get; set; }
public abstract class usrResidentialValidated : IBqlField { }
#endregion
}
}
Update
Thanks to some help from #Samvel I've modified the UpdateLocation code as follows. The following code does save the changes to the database (both on the custom field and the non-custom field), which is great. However, in order to do that, I had to create a new Location object "myLocation" and am no longer using the "location" object that the PXProcessing graph passed to UpdateLocation. This means that after processing, when the processing screen displays the processed records with the modified data (after processing finishes and before you refresh the screen), it does not show the updated values. Is there any way to both have the processing screen show the updated values and save the changes to the database?
public void UpdateLocation(PX.Objects.CR.Location location, bool isMassProcess = false)
{
bool isRes = true;
Location myLocation = PXSelect<Location,
Where<Location.bAccountID, Equal<Required<Location.bAccountID>>, And<Location.locationID, Equal<Required<Location.locationID>>>>>
.Select(this.Base, location.BAccountID, location.LocationID);
this.Base.Location.Current = myLocation;
LocationExt locationExt = myLocation.GetExtension<LocationExt>();
locationExt.UsrResidentialValidated = true;
myLocation.CResedential = isRes;
Base.Location.Current = Base.Location.Update(myLocation);
this.Base.Save.Press();
}
UPDATED
I have updated the code to correspond to your case. After processing all the records the records in the grid are being updated and showing modified records.
You can download the customization package for this code by this link
To create a processing page for updating Location you should do the following steps:
Add "Selected" field to the Location DAC
public sealed class LocationExt: PXCacheExtension<Location>
{
#region Selected
public abstract class selected : IBqlField
{ }
[PXBool()]
[PXDefault(true,PersistingCheck = PXPersistingCheck.Nothing)]
[PXUIField(DisplayName = "Selected")]
public bool? Selected { get; set; }
#endregion
#region UsrResidentialValidated
[PXDBBool]
[PXUIField(DisplayName = "Residential Validated")]
public bool? UsrResidentialValidated { get; set; }
public abstract class usrResidentialValidated : IBqlField { }
#endregion
}
This step is required because otherwise your delegate for SetProcessDelegate will never be called.
Acumatica is checking if there is at least one selected record before calling Process Delegate.
Create the Processing Graph like below:
using PX.Data;
using PX.Objects.CR;
using System.Collections.Generic;
namespace CustomerLocationUpdate
{
public class ProcessCustomerLocations : PXGraph<ProcessCustomerLocations>
{
public PXCancel<Location> Cancel;
public PXProcessingJoin<Location,InnerJoin<BAccountR,On<Location.bAccountID,Equal<BAccountR.bAccountID>>>,
Where<Location.isActive, Equal<True>,And<Location.locType, Equal<PX.Objects.CR.LocTypeList.customerLoc>>>> Locations;
public static void Process(List<Location> locations)
{
var graph = PXGraph.CreateInstance<PX.Objects.AR.CustomerLocationMaint>();
CustomerLocationMaint_Extension graphExt = graph.GetExtension<CustomerLocationMaint_Extension>();
foreach (var location in locations)
{
graphExt.UpdateLocation(location, true);
graph.Clear();
}
}
public ProcessCustomerLocations()
{
Locations.SetProcessDelegate(Process);
}
}
}
As you can see I have implicitly specified PX.Objects.AR and PX.Objects.CR for some reason the program has worked only this way on my instance.
Create the UpdateLocation method in the GraphExtension:
using PX.Data;
namespace CustomerLocationUpdate
{
public class CustomerLocationMaint_Extension : PXGraphExtension<PX.Objects.AR.CustomerLocationMaint>
{
public void UpdateLocation(PX.Objects.CR.Location location, bool isMassProcess = false)
{
bool isRes = false;
this.Base.Location.Current = PXSelect<PX.Objects.CR.Location,Where<PX.Objects.CR.Location.bAccountID,Equal<Required<PX.Objects.CR.Location.bAccountID>>,And<PX.Objects.CR.Location.locationID,Equal<Required<PX.Objects.CR.Location.locationID>>>>>.Select(this.Base,location.BAccountID,location.LocationID);
this.Base.Location.Current.CResedential = isRes;
LocationExt locationExt = PXCache<PX.Objects.CR.Location>.GetExtension<LocationExt>(this.Base.Location.Current);
locationExt.UsrResidentialValidated = false;
this.Base.Location.Current = this.Base.Location.Update(this.Base.Location.Current);
this.Base.Save.Press();
}
}
}
As you can see I am setting the Location.Current using PXSelect and not Location.Current.Search.
For some reason Location.Current.Search is always returning null.
May be it is caused by the PXProjectionAttribute applied to it, I am not sure what is the exact reason.
I am struggling with getting my Process Delegate to wire up correctly after implementing the advice pointed out in this ticket Implementing a DAC with no persisted fields. The processing page for the most part now behaves as we need. The records autoload based off of data pulled in via a ReST web service and we are not persisting any data to the ERP until the processing buttons are used. The issue I am having now is the SetProcessDeligate method is now not doing anything when I hit the process buttons. When I wire the older code into place that has one persistent field and requires a user to hit a load button the Process and Process All buttons work as expected.
I have created this screencast to walk through and give visual context to the issue. https://www.dropbox.com/s/j8vnp8p3556nj1e/issue%20with%20the%20PXProcessing%20page%20not%20wiring%20into%20the%20event%20handler%202019-01-03_12-57-50.mp4?dl=0
As always I am very grateful for any help. Thank you
Robert
//This is how my Graph is defined now.
public class CtpPaymentProcess : PXGraph<CtpPaymentProcess>
{
//public PXAction<CtpPayment> checkForC2PPayments;
public PXSetup<CtpSetup> setup;
public PXProcessing<CtpPayment> Payments;
public QueryPaymentsResponseViewModel paymentsFromCtpServer { get; internal set; }
public IEnumerable payments()
{
paymentsFromCtpServer = CtpAcumatica.CheckForAllNewPayments(100);
PXTrace.WriteInformation("Processing " + (paymentsFromCtpServer.Payments.Count) + " paymentsFromCtpServer");
if (paymentsFromCtpServer.Payments != null)
{
// Loop processing each payment returned from the gateway, storing the
// information into non persisted cache.
foreach (var payment in paymentsFromCtpServer.Payments)
{
if (!payment.IsMarkedRetrieved)
{
yield return BuildCtpPayment(payment);
}
}
}
}
private CtpPayment BuildCtpPayment(PaymentViewModel payment)
{
var customer = (Customer)PXSelect<Customer,
Where<Customer.bAccountID, Equal<Required<Customer.bAccountID>>>>
.Select(this, payment.CustomerId).FirstOrDefault();
//Todo: add assertion that will assert payment is made to only matching company payment.CompanyId
//Todo: find out if we need to handel Bank Account Types differently payment.BankAccountType
DateTime.TryParse(payment.Date, out var payDate);
return new CtpPayment
{
CustomerID = int.Parse(payment.CustomerId),
Customer = $"{customer.AcctCD}:{customer.AcctName}",
Amount = payment.Amount,
Description = $"Payment:{payment.Id}",
Id = payment.Id,
ApsTransactionID = payment.ApsTransactionId,
Currency = payment.Currency,
PaymentDate = payDate,
Invoices = InvoicesAsString(payment)
};
}
private static string InvoicesAsString(PaymentViewModel payment)
{
var Invoices = payment.Invoices.Select(x => x.InvoiceId);
StringBuilder stringBuilder = new StringBuilder();
foreach (string inv in Invoices)
{
stringBuilder.AppendFormat("{0} ", inv);
}
string result = stringBuilder.ToString();
if (result.Length > 100) result = result.Substring(0, 96) + "...";
return result;
}
private CtpAcumatica _ctpAcumatica;
public CtpAcumatica CtpAcumatica
{
get
{
if (_ctpAcumatica == null)
{
var graph = PXGraph.CreateInstance<PXGraph>();
_ctpAcumatica = new CtpAcumatica(setup.Current.CtpUrl,
setup.Current.CtpApiKey,
"NoLongerNeeded", //todo: refactor this out.
"NoLongerNeeded", //todo: refactor this out.
graph);
}
return _ctpAcumatica;
}
}
public CtpPaymentProcess()
{
Payments.SetProcessCaption("Process Payments");
Payments.SetProcessAllCaption("Process All Payments");
Payments.SetProcessDelegate<CtpPaymentProcess>(
delegate (CtpPaymentProcess graph, CtpPayment payment)
{
graph.Clear();
graph.ProcessPayment(payment, true);
}
);
//Alternate attempt proved un-successful
//Payments.SetProcessDelegate(PaymentGenerationDelegate);
}
/* implemented as a test. will remove from production code
private void PaymentGenerationDelegate(List<CtpPayment> list)
{
foreach (var payment in list)
{
ProcessPayment(payment, true);
}
}
*/
private void ProcessPayment(CtpPayment payment, bool massProcess)
{
PXTrace.WriteInformation($"Processing {payment}");
//for now we will only write to the trace window.
//Stopwatch stopWatch = new Stopwatch();
//stopWatch.Start();
//createPayment(payment);
//stopWatch.Stop();
//PXTrace.WriteInformation($"Payment {payment.ApsTransactionID} finished in {stopWatch.Elapsed.TotalSeconds} Seconds");
}
//todo: unfinished
private void createPayment(CtpPayment paymentData)
{
var paymentFromCtp = CtpAcumatica.GetPaymentRecord(long.Parse(paymentData.Id));
ARPaymentEntry arPaymentEntry = PXGraph.CreateInstance<ARPaymentEntry>();
ARPayment payment = new ARPayment
{
CustomerID = int.Parse(paymentFromCtp.CustomerId),
CuryOrigDocAmt = paymentData.Amount
};
arPaymentEntry.CurrentDocument.Insert(payment);
foreach (var invoice in paymentFromCtp.Invoices)
{
ARAdjust adj = new ARAdjust
{
AdjdRefNbr = invoice.InvoiceId,
CuryAdjgAmt = invoice.Amount
};
arPaymentEntry.Adjustments.Insert(adj);
}
arPaymentEntry.Persist();
PXTrace.WriteInformation(arPaymentEntry.ToString());
}
}
//This is the DAC definition.
[Serializable]
[PXPrimaryGraph(typeof(CtpPaymentProcess))]
//[PXNonInstantiatedExtension] this looked close
//to what we are looking for but experimenting
//with it did not yield desired results.
public class CtpPayment : IBqlTable
{
#region Selected
public abstract class selected : IBqlField{ }
[PXBool]
[PXUIField(DisplayName = "Selected")]
public virtual bool? Selected { get; set; }
#endregion
public abstract class id : IBqlField { }
//todo: find out what size we need 50 is just a guess.
//[PXString(50, IsKey = true)] //We are able to get this to work only if
//we have at least one persisting field.
//we can live with this but would prefer to
//have the whole class as non-persistent
[PXString(50,IsKey = true)] //having only non persisting attributes will result in a
//Incorrect syntax near the keyword 'FROM'. error.
[PXUIField(DisplayName = "Click To Pay Id")]
public virtual string Id { get; set; }
public abstract class customer : IBqlField { }
[PXString(100)]
[PXUIField(DisplayName = "Customer")]
public virtual string Customer { get; set; }
public abstract class description : IBqlField {}
[PXString(200)]
[PXUIField(DisplayName = "Payment Description")]
public virtual string Description { get; set; }
public abstract class amount : IBqlField { }
[PXDecimal(2)]
[PXUIField(DisplayName = "Payment Amount")]
public virtual decimal? Amount { get; set; }
public abstract class customerId : IBqlField { }
[PXInt]
[PXUIField(DisplayName = "Customer ID")]
//todo: decorate this with the needed attributes to display friendly key instead of int.
public virtual int? CustomerID { get; set; }
public abstract class apsTransactionID : IBqlField { }
[PXString]
[PXUIField(DisplayName = "Transaction ID")]
public virtual string ApsTransactionID { get; set; }
public abstract class currency : IBqlField { }
[PXString(10)]//todo: determine best size. 10 is a guess.
[PXUIField(DisplayName = "Currency")]
public virtual string Currency { get; set; }
public abstract class paymentDate : IBqlField { }
[PXDate]
[PXUIField(DisplayName = "Payment Date")]
public virtual DateTime? PaymentDate { get; set; }
public abstract class invoices : IBqlField { }
[PXString(100)]
[PXUIField(DisplayName = "Invoices")]
public virtual string Invoices { get; set; }
}
Thanks, HB_Acumatica for the direction on this.
Changing my code to use the following got to the end result I needed. I Hope this helps someone in the future.
//old implementation that would not render any result when the process buttons where clicked.
//public IEnumerable payments()
//{
// paymentsFromCtpServer = CtpAcumatica.CheckForAllNewPayments(100);
// PXTrace.WriteInformation("Processing " + (paymentsFromCtpServer.Payments.Count) + " paymentsFromCtpServer");
// if (paymentsFromCtpServer.Payments != null)
// {
// // Loop processing each payment returned from the gateway, storing the
// // information into non persisted cache.
// foreach (var payment in paymentsFromCtpServer.Payments)
// {
// if (!payment.IsMarkedRetrieved)
// {
// yield return BuildCtpPayment(payment);
// }
// }
// }
//}
public IEnumerable payments()
{
paymentsFromCtpServer = CtpAcumatica.CheckForAllNewPayments(100);
PXCache cache = Caches[typeof(CtpPayment)];
cache.AllowInsert = false;
cache.AllowUpdate = false;
if (cache.Current == null)
{
foreach (var payment in paymentsFromCtpServer.Payments)
{
if (!payment.IsMarkedRetrieved)
{
cache.SetStatus(BuildCtpPayment(payment), PXEntryStatus.Held);
}
}
}
return Payments.Cache.Cached;
}
I have been trying to add custom DAC record which is the in database. But it is now working. Here is how I have tried to accomplish.
public class SquarePOSTransactionInquiry : PXGraph<SquarePOSTransactionInquiry>
{
public PXSave<MasterTable> Save;
public PXCancel<MasterTable> Cancel;
public PXFilter<MasterTable> MasterView;
public PXSelect<INSquarePOSTransaction> INSquarePOSTransactions;
public PXAction<MasterTable> calc;
[PXUIField(DisplayName = "Sync Square Transactions")]
[PXProcessButton()]
protected virtual IEnumerable Calc(PXAdapter adapter)
{
PXLongOperation.StartOperation(this, () =>
{
using (var scope = new PXTransactionScope())
{
INSquarePOSTransaction trans = new INSquarePOSTransaction();
trans.TransacationCD = "new";
trans.Description = "Another new";
var test = this.INSquarePOSTransactions.Insert(trans);
this.INSquarePOSTransactions.Cache.IsDirty = true;
//this.INSquarePOSTransactions.Update(trans);
this.Actions.PressSave();
scope.Complete();
}
});
return adapter.Get();
}
public SquarePOSTransactionInquiry()
{
}
[Serializable]
public class MasterTable : IBqlTable
{
}
}
I tried setting cache IsDirty property to false, but that didn't help too. But the strange part is updating the DAC is working. I have even looked into other Business Logic codes from other pages and it looks same like I have tried above. Could you please tell me what I am missing?
Thanks.
Within the method that you pass to StartOperation(),
you have to create a new instance of the graph and invoke the processing method on that instance.
I am new at automapper and it is a very good stuff easy to use, but now I have a problem with it. Trying to convert my derived class to base and it gives me
AutoMapper.AutoMapperMappingException
Missing type map configuration or unsupported mapping.
Mapping types: ClientEventDb -> EventId
Database.ClientEventDb -> EventId
Destination path: ClientEvent
Source value:
Event:Login
Automapper wants to convert ClientEventDb to EventId? I don't understand why. EventId is an enum...
Please help me I have run out of ideas.
Here is the code which I run:
ClientEventDb[] edbl;
using (var context = new DbEntities())
{
edbl=context.Events.Take(1000).ToArray();
}
Mapper.CreateMap<ClientEventDb, ClientEvent>();
Console.WriteLine("hello");
return edbl.Select(edb => Mapper.Map<ClientEvent>(edb)).ToArray();
Here are my classes
[Table("events", Schema = "public")]
public class ClientEventDb : ClientEvent
{
public ClientEventDb(string userName, EventId happening, object userObject = null)
: base(userName, happening, userObject)
{
}
public ClientEventDb()
{
}
}
[ProtoContract]
[Table("events", Schema = "public")]
public class ClientEvent : ClientEventBase
{
[ProtoMember(1)]
[Column("username")]
public string UserName { get; private set; }
[ProtoMember(2)]
[Column("time")]
public DateTime DateTime { get; private set; }
[ProtoMember(3)]
[Key]
[Column("id")]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public string Id { get; private set; }
[ProtoMember(4)]
[Column("data")]
public byte[] UserObject { get; set; }
public ClientEvent(string userName,EventId happening, object userObject=null) : base(happening)
{
UserName = userName;
DateTime = DateTime.Now;
//UserObject = null;
if (userObject!=null) throw new NotImplementedException();
}
public ClientEvent()
{
}
protected ClientEvent Clone()
{
return (ClientEvent)MemberwiseClone();
}
}
[ProtoContract]
[ProtoInclude(10, typeof(ClientEvent))]
public class ClientEventBase
{
[Column("eventid")]
[ProtoMember(1)]
public int EventIdValue { get; set; } //must be public because of entity framework
[NotMapped]
public EventId EventId
{
get { return (EventId) EventIdValue; }
set { EventIdValue = (int) value; }
}
public ClientEventBase(EventId eventId)
{
EventId = eventId;
}
public ClientEventBase()
{
}
public override string ToString()
{
return String.Format("Event:{0}",EventId);
}
}
public enum EventId
{
Login = 1,
Logout,
ExitApplication,
}
UPDATE
bugfix: ClientEvent [Key] attribute moved to id property
Solution was this (thx to stuartd):
ClientEventDb[] edbl;
using (var context = new DbEntities())
{
edbl=context.Events.ToArray();
}
Mapper.CreateMap<ClientEventDb, ClientEvent>().ConstructUsing((ClientEventDb src) => new ClientEvent());
return edbl.Select(Mapper.Map<ClientEvent>).ToArray();
AutoMapper is confused as its made to map between similar properties in different classes, you are using it incorrectly - you just need to go from the derived class to the base which does not require AutoMapper. You could use this to do what you need....
ClientEventDb[] edbl;
using (var context = new DbEntities())
{
edbl=context.Events.Take(1000).ToArray();
}
return edbl.Cast<ClientEvent>().ToList();
I'd be looking at why you even feel you need a derived ClientEventDb though - understand we dont have the whole picture here but it seems to do nothing in addition to what the base class already does.
The issue is that ClientEvent has two constructors but you have not told AutoMapper which to use.
If you want it to use your constructor with parameters, change your mapping code to this and it will work:
Mapper.CreateMap<ClientEventDb, ClientEvent>()
.ConstructUsing(src => new ClientEvent(src.UserName, src.EventId));
Or to make AutoMapper use the default constructor:
Mapper.CreateMap<ClientEventDb, ClientEvent>()
.ConstructUsing((ClientEventDb src) => new ClientEvent());
I have small WPF application. There are 5 projects in solution.
I want separate DOMAIN classes with UI ENTITIES and I want to use AUTOMAPPER.
You can download whole solution here: TestWPFAutomapper.zip
Domain class(Domain.Source.cs) with UI Entity(Entities.Destination.cs) have same signature.
In Entities.Destination.cs I would like to put other logic.
namespace DOMAIN
{
public class Source
{
public int Id { get; set; }
public int Position { get; set; }
}
}
using System.ComponentModel;
namespace ENITITIES
{
public class Destination : INotifyPropertyChanged
{
private int _id;
private int _position;
public int Id
{
get { return _id; }
set
{
_id = value;
OnPropertyChanged("Id");
}
}
public int Position
{
get { return _position; }
set
{
_position = value;
OnPropertyChanged("Position");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
My data comes from DAL.DataContext using Entity Framework with CodeFirst. Here I´m using Source class.
using System.Data.Entity;
using DOMAIN;
namespace DAL
{
public class DataContext : DbContext
{
public DbSet<Source> Sources { get; set; }
}
}
Mapping is in BL.MyAppLogic.cs . In this class I have property Items which is ObservableCollection.
After puting another item into DB for Source class collection get refresh but for Destination is not refreshing.
using System.Collections.ObjectModel;
using System.Data.Entity;
using System.Linq;
using AutoMapper;
using DAL;
using DOMAIN;
using ENITITIES;
namespace BL
{
public class MyAppLogic
{
private readonly DataContext _dataContext = new DataContext();
public ObservableCollection<Source> Items { get; set; }
//public ObservableCollection<Destination> Items { get; set; }
public MyAppLogic()
{
Database.SetInitializer(new MyInitializer());
Mapping();
_dataContext.Sources.Load();
Items = _dataContext.Sources.Local;
//Items = Mapper.Map<ObservableCollection<Source>, ObservableCollection<Destination>>(_dataContext.Sources.Local);
}
private void Mapping()
{
Mapper.CreateMap<Source, Destination>().ReverseMap();
// I tried also Mapper.CreateMap<ObservableCollection<Source>, ObservableCollection<Destination>>().ReverseMap();
}
public int GetLastItem()
{
return _dataContext.Database.SqlQuery<int>("select Position from Sources").ToList().LastOrDefault();
}
public void AddNewItem(Destination newItem)
{
_dataContext.Sources.Add(Mapper.Map<Destination, Source>(newItem));
_dataContext.SaveChanges();
}
}
}
My problem is not with mapping, that’s works good, but with refreshing collection after adding or removing items from db. If I use DOMAIN.Source class everything works, collection is refreshing. But when I’m using ENTITIES.Destination data comes from DB and also I can put som new data to DB but refresing ObservableCollection is not working.
Please try to comment lines(14 & 23) in BL.MyAppLogic.cs and uncomment(15 & 24) and you’ll see what I mean.
Thank you for any help.
I got it but I don´t know if is correct.
Local has CollectionChanged event
so in constructor I put these lines
public MyAppLogic()
{
Database.SetInitializer(new MyInitializer());
Mapping();
_dataContext.Sources.Load();
_dataContext.Sources.Local.CollectionChanged += SourcesCollectionChanged;
Items = Mapper.Map<ObservableCollection<Source>, ObservableCollection<Destination>>(_dataContext.Sources.Local);
}
and handler looks
private void SourcesCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
var source = sender as ObservableCollection<Source>;
Mapper.Map(source, Items);
}
Now is my collection automating refreshing when I put something to DB in my UI.
Looks like automapper don´t put reference into Items, but create new instance.