I have 3 Oracle databases; production, test, development. For the most part, they are all identical. In my application, I would like the changes to be applied to multiple databases. For example:
using (var context = new Context())
{
context.People.Add(new Person { name = "sean" });
context.SaveChanges();
}
I then tried to override the SaveChanges method and save to multiple databases by doing this:
public void SaveChanges(int auditPersonNumber)
{
OracleCredentials.Default.Server = "VDev";
base.SaveChanges();
OracleCredentials.Default.Server = "VTest";
base.SaveChanges();
OracleCredentials.Default.Server = "VProd";
base.SaveChanges();
}
This didn't work but should explain what I am trying to achieve.
I haven't yet used EntityFramework against an Oracle database, but it should be similar to connecting against SQL Server in that the database name is specified via a ConnectionString. Your project should have a config file (web.config, app.config, or if it's a .NET Core application it could be in appsettings.json) with that ConnectionString in it.
For example:
<add name="YourConnectionString" providerName="YourOracleProviderName" connectionString="User Id=test;Password=testpassword;Data Source=eftest" />
The DbContext base constructor accepts a string argument that specifies which ConnectionString it should use, and thus which database to connect to. If you look into your context class, the default constructor should call the base constructor with that argument.
public YourDbContext() : base("YourConnectionString") {}
In order to save to multiple databases you will need to work against different instances of DbContext each with a different ConnectionString argument. So, your config will need to list a few different connection strings for every Db and you'll probably want your DbContext class to allow the argument in its constructor as well.
Perhaps the SaveChanges method implementation could instantiate the other DbContexts you'd need to use:
public void SaveChanges(int auditPersonNumber)
{
using (var context = new Context("OtherConnectionString1"))
{
// apply same changes
context.SaveChanges();
}
using (var context = new Context("OtherConnectionString2"))
{
// apply same changes
context.SaveChanges();
}
base.SaveChanges();
}
As for the applying the same changes, I would expect you can read them out from the DbContext ChangeTracker. There's an explanation about that using EF Core here but in earlier versions it's similar: http://www.entityframeworktutorial.net/efcore/changetracker-in-ef-core.aspx
Also keep in mind that the SaveChanges call to OtherConnectionString1 could succeed while others could fail, so the data might be inconsistent in your different databases. You may have to look into using transactions across multiple databases but I haven't done this yet myself.
I was able to figure out a solution thanks to the help of Sangman.
public class Context : Shared.Data.Context
{
new public void SaveChanges(int auditPersonNumber)
{
var errors = string.Empty;
var testConnectionString = "ConnectionString";
var developmentConnectionString = "ConnectionString";
//Save to test database
if (SecurityMaintenanceUser.ApplyToTest)
errors = ApplyToDatabase(testConnectionString, auditPersonNumber, "Test");
if (!string.IsNullOrWhiteSpace(errors))
errors += "\n\n";
//Save to development database
if (SecurityMaintenanceUser.ApplyToDevelopment)
errors += ApplyToDatabase(developmentConnectionString, auditPersonNumber, "Development");
if (!string.IsNullOrWhiteSpace(errors))
MessageBox.Show(errors, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
//Save to production database
base.SaveChanges(auditPersonNumber);
}
private string ApplyToDatabase(string connectionString, int auditPersonNumber, string server)
{
try
{
using (var context = new Context(connectionString))
{
context.Configuration.ValidateOnSaveEnabled = false;
foreach (var entry in ChangeTracker.Entries())
{
var dataSet = context.Set(entry.Entity.GetType());
if (entry.State == EntityState.Added)
{
dataSet.Add(entry.Entity);
}
else if (entry.State == EntityState.Deleted)
{
var contextEntity = dataSet.Find(GetPrimaryKeyValues(entry));
context.DeleteEntity(contextEntity, auditPersonNumber);
}
else if (entry.State == EntityState.Modified)
{
var contextEntity = dataSet.Find(GetPrimaryKeyValues(entry));
context.Entry(CopyProperties(entry.Entity, contextEntity)).State = EntityState.Modified;
}
}
context.SaveChanges(auditPersonNumber);
return string.Empty;
}
}
catch (Exception e)
{
return $"Failed to apply database changes to {server}.\n{e.GetFullMessage()}";
}
}
private object CopyProperties(object source, object destination)
{
if (source == null || destination == null)
throw new Exception("Source or/and Destination Objects are null");
var typeDest = destination.GetType();
var typeSrc = source.GetType();
foreach (var srcProp in typeSrc.GetProperties())
{
if (srcProp.Name == "Type" || srcProp.Name == "AuthenticationLog")
continue;
//This blocks any complex objects attached to the entity, will need to be changed for your application
if (srcProp.PropertyType.FullName.Contains("Library.Shared"))
continue;
if (!srcProp.CanRead)
continue;
var targetProperty = typeDest.GetProperty(srcProp.Name);
if (targetProperty == null)
continue;
if (!targetProperty.CanWrite)
continue;
if (targetProperty.GetSetMethod(true)?.IsPrivate == true)
continue;
if ((targetProperty.GetSetMethod().Attributes & MethodAttributes.Static) != 0)
continue;
if (!targetProperty.PropertyType.IsAssignableFrom(srcProp.PropertyType))
continue;
targetProperty.SetValue(destination, srcProp.GetValue(source, null), null);
}
return destination;
}
private object GetPrimaryKeyValues(DbEntityEntry entry)
{
var objectStateEntry = ((IObjectContextAdapter)this).ObjectContext.ObjectStateManager.GetObjectStateEntry(entry.Entity);
return objectStateEntry.EntityKey.EntityKeyValues[0].Value;
}
public static string GetFullMessage(this Exception ex)
{
return ex.InnerException == null ? ex.Message : $"{ex.Message}\n{ex.InnerException.GetFullMessage()}";
}
public static string Replace(this string source, string oldString, string newString, StringComparison comp)
{
int index = source.IndexOf(oldString, comp);
if (index >= 0)
{
source = source.Remove(index, oldString.Length);
source = source.Insert(index, newString);
}
if (source.IndexOf(oldString, comp) != -1)
source = Replace(source, oldString, newString, comp);
return source;
}
}
We are creating a spring and hibernate application and using a legacy database.
Our requirement is to get values from few database tables on server startup.
We are planning to put these values in properties files.So that we don't need to fetch DB for these values again and again.
We have used ApplicationListener to get hook on startup using following stackoverflow question:-
Listener for server starup and all spring bean loaded completely
the code being used is as below
#Component
public class SpringContextListener implements ApplicationListener<ContextRefreshedEvent> {
private List<Yosemitecompany> companyList = new ArrayList<Yosemitecompany>();
private YosemitecompanyRI iYosemitecompanyBO;
public SpringContextListener(){
}
public SpringContextListener(YosemitecompanyRI iYosemitecompanyBO) {
this.iYosemitecompanyBO = iYosemitecompanyBO;
}
public void onApplicationEvent(final ContextRefreshedEvent event) {
System.out.println("ApplicationListener Started"+iYosemitecompanyBO);
if(companyList == null || (companyList != null && companyList.size() <= 0) && iYosemitecompanyBO != null)
{
companyList = iYosemitecompanyBO.getCompanyDetailsWithStatus();
}
}
public List<Yosemitecompany> getCompanyList()
{
return companyList;
}
}
and this is the repository class
#Repository
#Transactional
public class YosemitecompanyRI implements IYosemitecompanyR{
static final Logger log = Logger.getLogger("YosemitecompanyDAOI");
#Autowired
private SessionFactory sessionFactory;
protected Session getSession() {
log.info(sessionFactory);
if (sessionFactory != null)
return sessionFactory.getCurrentSession();
else
return null;
}
#Override
public List<Yosemitecompany> getCompanyDetailsWithStatus()
{
List<Yosemitecompany> results = new ArrayList<Yosemitecompany>();
log.info("reached "+getSession());
if(getSession() != null)
{
log.info("executing query");
Criteria cr = getSession().createCriteria(Yosemitecompany.class);
cr.add(Restrictions.eq("cmpstatus",new BigDecimal(1)));
results = (List<Yosemitecompany>)cr.list();
}
return results;
}
}
Now on server startup..i get sessionFactory always as null..so my code for getting the list never gets executed.
i am new to spring and Hibernate.If this approach is fine then please help me to know what i am doing wrong.if there is a better approach to achieve please suggest that too.
Thanks in advance.
I have a WCF service that I am self-hosting in a WinForm application. In the application I have a ListBox control to display diagnostic information. I pass a reference to the ListBox control to the WCF service. In that service there is a callback that uses it.
For some reason if I run the application outside of Visual Studio, I am able to use the ListBox to display information in the main hosting application, all methods in the WCF service, and the single callback in the WCF service. But, running in Visual Studio, the same application fails when trying to write info while in the callback.
Am I doing something that I fundamentally cannot do and getting away with it OR is there something incorrect going on while running in Visual Studio?
Here is part of my WinForm host
public WCFServiceHostGUI()
{
InitializeComponent();
// OutputBox is a ListBox control reference. Pass to WCF service to display diagnostics
WCFCallbacks.UsbBrokerService.initialize(OutputBox);
startService();
}
private void startService()
{
usbBrokerServiceHost = new ServiceHost(typeof(WCFCallbacks.UsbBrokerService));
ServiceDescription serviceDesciption = usbBrokerServiceHost.Description;
foreach (ServiceEndpoint endpoint in serviceDesciption.Endpoints)
{
OutputBox.Items.Add("Endpoint - address: " + endpoint.Address);
OutputBox.Items.Add(" - binding name: " + endpoint.Binding.Name);
OutputBox.Items.Add(" - contract name: " + endpoint.Contract.Name);
}
usbBrokerServiceHost.Open();
/*
ChannelFactory<WCFCallbacks.IUsbBroker> channelFactory = new ChannelFactory<WCFCallbacks.IUsbBroker>(BINDING, ADDRESS);
WCFCallbacks.IUsbBroker clientProxy = channelFactory.CreateChannel();
clientProxy.initialize(OutputBox);
*/
}
In the constructor a reference to the ListBox is passed to the WCF service. One can see the usual creation of the ServiceHost in the startService() method.
Here is the WCF service that gets the ListBox reference
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
public class UsbBrokerService : IUsbBroker
{
private static readonly List<IUsbBrokerCallback> subscribers = new List<IUsbBrokerCallback>();
private static ListBox OutputBox = null;
public UsbBrokerService()
{
Console.WriteLine("Service started");
UsbDevicePluginIndicator.DeviceNotify += new UsbDevicePluginIndicator.DeviceNotifyDelegate(UsbDevicePluginIndicator_DeviceNotify);
UsbDevicePluginIndicator.Start();
}
public static void initialize(ListBox outputBox)
{
OutputBox = outputBox;
}
public void AddMessage(string message)
{
// Use the following to see which endpoint is accessed
OperationContext oc = OperationContext.Current;
if (oc != null)
{
Console.WriteLine("A request was made on endpoint " + oc.Channel.LocalAddress.ToString());
if (OutputBox != null)
{
OutputBox.Items.Add("A request was made on endpoint " + oc.Channel.LocalAddress.ToString());
}
}
}
public bool RegisterDevices(UsbDevice[] usbDevices)
{
try
{
IUsbBrokerCallback callback = OperationContext.Current.GetCallbackChannel<IUsbBrokerCallback>();
if (!subscribers.Contains(callback))
{
subscribers.Add(callback);
}
return true;
}
catch
{
return false;
}
}
public bool UnRegisterDevices(UsbDevice[] usbDevices)
{
try
{
IUsbBrokerCallback callback = OperationContext.Current.GetCallbackChannel<IUsbBrokerCallback>();
if (subscribers.Contains(callback))
{
subscribers.Remove(callback);
}
return true;
}
catch
{
return false;
}
}
private void UsbDevicePluginIndicator_DeviceNotify(System.Windows.Forms.Message msg)
{
BroadcastHeader lBroadcastHeader;
Console.WriteLine("Wcf WM_DEVICECHANGE signaled");
if (OutputBox != null)
{
OutputBox.Items.Add("Wcf WM_DEVICECHANGE signaled");
}
}
}
In the callback routine UsbDevicePluginIndicator_DeviceNotify the attempt to write to OutputBox fails with "Cross-thread operation not valid: Control 'OutputBox' accessed from a thread other than the thread it was created on." but ONLY while running in Visual Studio.
So am I doing something fundamentally wrong?
In my application, When one user will use a bean object it's life cycle have to be maintained as session in controller class also. Different user have to be different bean object. So I add session scope like:
<bean id="constants" class="com.project.barlexamquize.model.Constants" scope="session" >
<aop:scoped-proxy/>
</bean>
It works fine in local PC checked in different browser at a same time. But it does not work while this apps is deployed in Google App Engine. Here it behaves like request scope ie. in each http request it create new bean object. But my needs to make the bean session one time initialize for each user. My code are given bellow. Please visit: my site
#Controller
#RequestMapping("/barlexamquize")
public class BarlExamQuizeController {
public static long totalQuestion=390;
#Autowired Constants constants;
#RequestMapping(value = "/giveAnswer", method = RequestMethod.GET)
public String practiceQuestions(HttpServletRequest request, ModelMap model) {
PersistenceManager pm=null;
Query q=null;
List<BarlQuestion> barlQuestion = null;
pm = PMF.get().getPersistenceManager();
q= pm.newQuery(BarlQuestion.class, "id == idParameter");
q.declareParameters("long idParameter");
try {
barlQuestion = (List<BarlQuestion>) q.execute(constants.getId());
if (barlQuestion.isEmpty()) {
model.addAttribute("barlQuestion", null);
} else {
model.addAttribute("barlQuestion", barlQuestion.get(0));
}
} finally {
q.closeAll();
pm.close();
}
return "giveAnswer";
}
#RequestMapping(value = "/checkAnswer", method = RequestMethod.POST)
public String checkQuestionAnswer(#RequestParam String result,
ModelMap model) {
PersistenceManager pm = PMF.get().getPersistenceManager();
Query q = pm.newQuery(BarlQuestion.class, "id == idParameter");
q.declareParameters("long idParameter");
List<BarlQuestion> barlQuestion = null;
try {
barlQuestion = (List<BarlQuestion>) q.execute(constants.getId());
if (result.equals(barlQuestion.get(0).getAnswer())) {
constants.setRs("Right Choice");
constants.incrementRightAns();
} else
constants.setRs("Wrong!!! Correct: " + barlQuestion.get(0).getAnswer());
model.addAttribute("status", constants.getRs());
} finally {
q.closeAll();
pm.close();
}
return "questionResult";
}
#RequestMapping(value = "/nextQuestion", method = RequestMethod.POST)
public String nextQuestion(HttpServletRequest request, ModelMap model) {
PersistenceManager pm = PMF.get().getPersistenceManager();
Query q = pm.newQuery(BarlQuestion.class, "id == idParameter");
q.declareParameters("long idParameter");
List<BarlQuestion> barlQuestion = null;
if (constants.getId() < totalQuestion) {
constants.incrementId();
try {
barlQuestion = (List<BarlQuestion>) q.execute(constants.getId());
if (barlQuestion.isEmpty()) {
model.addAttribute("barlQuestion", null);
} else {
model.addAttribute("barlQuestion", barlQuestion.get(0));
}
} finally {
q.closeAll();
pm.close();
}
return "giveAnswer";
} else {
String score = ((constants.getRightAns() * 100) / totalQuestion) + "%";
model.addAttribute("score", score);
constants.setRightAns(0);
constants.setId(1);
return "examScore";
}
}
}
Please help me to find the problems of my code. For understanding my thoughts, My application has a Datastore of questions. Application shows one question at a time and take answer of that question, then shows next question. In the controller the constants.getId() is the concern. It is increment in the next question. So the incremental value should be remain in one session.
Make the Constants bean Serializable.
I want to use the memcache in google app engine and i implemented like this :
public static UserDictionary userDictionary = null;
private MemcacheService syncCache;
public static final Logger log = Logger.getLogger(UserDictionary.class.getName());
#SuppressWarnings(value = { "unchecked" })
private UserDictionary() {
syncCache = MemcacheServiceFactory.getMemcacheService();
syncCache.setErrorHandler(ErrorHandlers.getConsistentLogAndContinue(Level.INFO));
}
public static UserDictionary getInstance() {
if(userDictionary == null) {
userDictionary = new UserDictionary();
}
return userDictionary;
}
public Entity getFromCache(String userId) {
return (Entity) syncCache.get(userId);
}
public void putInCache(String userId, Entity userEntity) {
syncCache.put(userId, userEntity,Expiration.byDeltaSeconds(999999999), MemcacheService.SetPolicy.SET_ALWAYS);
}
public boolean isCacheEmpty() {
long itemCount = syncCache.getStatistics().getItemCount();
return itemCount == 0L;
}
The problem is that after i put an Object in cache and then i try to get it, the get method return null. I looked in my google app engine and it shows that is an object in memcache.The key is a java String. Both key and value implements Serializable.
Hit count: 0
Miss count: 31522
Hit ratio: 0%
Item count: 15 item(s)