I have a few tables in SQL Server that I am using to log when a user logs into a Silverlight application.
I have created an entity for each of those tables. One example is ApplicationUsageLog, where I log the ApplicationID, the Date, and the UserID. Those are mostly pulled from the Silverlight side.
I would like to just create a method called Login(AppID,UserID) that can do an insert into that table.
Is that possible?
Thanks!
EDIT The following does not work for some reason:
[Invoke]
public void Login(int AppID,string EmployeeNo)
{
var aul = new ApplicationUsageLog{ ApplicationID = AppID, LoginDate = System.DateTime.Now, EmployeeNo = EmployeeNo };
if ((aul.EntityState != System.Data.EntityState.Detached))
{
this.ObjectContext.ObjectStateManager.ChangeObjectState(aul, System.Data.EntityState.Added);
}
else
{
try
{
this.ObjectContext.ApplicationUsageLogs.AddObject(aul);
}
catch (System.Exception e) { }
}
}
I can look at aul and all looks good. But when I put a breakpoint at the end, this.ObjectContext.ApplicationUsageLogs is still totally empty....
Yes, you can add a method to your Domain service class marked with the InvokeAttribute attribute. The method will appear as a method on the client context class.
You can add your own custom methods to the DomainService if you want but it will not be called automatically when you insert into your context and SubmitChanges. You will have to call it manually.
If you want to override the implementation of your Insert method you can simply modify the contents of the Insert*EntityName* method in your DomainService.
Related
Here is my current problem. Lets say I have a DBConext for Customers called CustomerContext and on for Employees called EmployeeContext. I have them in two different DBContexts to keep it smaller and simplified. When I want to create a new customer, I call my Customers BLL(Business Logic Layer) which will in turn create a new instance of the CustomerContext. Now that context will be passed to a other related BLLs to verify the information and add their own records to the context to be inserted. when a context is passed into a BLL, that BLL will not save the changes to the context, the Parent level procedure will do that.
This works fine except when I pass the context to the Notes BLL. Here If I define the context as CustomerContext I am okay, but I need to also use this with EmployeeContext. How can I pass the Context in and determine which one to use at runtime? I tried passing it in as an object, but then if I try to add it to the table, I get object does not contain a definition for Note. Any suggestions would be greatly appreciated.
Here is a sample of what I want it to do, but I get an error on the moContext.Notes.Add(oNote); line, because its an object and not the context. moContext could be either CustomerContext or EmployeeContext.
object moContext;
bool mbContextCreatedLocal;
public NotesDAL(ref object pContext)
{
moContext = pContext;
mbContextCreatedLocal = false;
}
public void InsertNote(Note pNote)
{
Note oNote = null;
oNote = new Note()
{
Note = pNote.Note.Trim(),
NoteCategoryID = pNote.NoteCategoryID,
Title = (string.IsNullOrEmpty(pNote.Title) ? null : pNote.Title.Trim()),
};
moContext.Notes.Add(oNote);
if (mbContextCreatedLocal )
{
moContext.SaveChanges();
}
}
In an ASP.NET MVC application, I'm trying to use SQL Server's CONTEXT_INFO to pass the currently logged in user so my audit triggers record not only the web server login, but also the login of the site.
I'm having trouble being certain that the current user will always be fed into the database server context though.
On the backend I have everything set up, a sproc to set the context, a function to pull it and DML triggers to record, no problem.
The app end is a bit more involved. I subscribe to the Database.Connection.StateChange event so I can catch each newly opened connection and set this context accordingly.
Additionally, to be able to retrieve the current login ID of the MVC site in the data layer (which has no access to the web project), I supply a delegate to the EF constructor that will return the user ID. This also means that any other peripheral projects I have set up require this dependency as well, and it keeps most of the implementation detail out of my hair during the web dev:
public class CoreContext : DbContext
{
Func<int> _uidObtainer;
public CoreContext(Func<int> uidObtainer) : base(nameof(CoreContext)) { construct(uidObtainer); }
public CoreContext(Func<int> uidObtainer, string connection) : base(connection) { construct(uidObtainer); }
void construct(Func<int> uidObtainer) {
// disallow updates of the db from our models
Database.SetInitializer<CoreContext>(null);
// catch the connection change so we can update for our userID
_uidObtainer = uidObtainer;
Database.Connection.StateChange += connectionStateChanged;
}
private void connectionStateChanged(object sender, System.Data.StateChangeEventArgs e) {
// set our context info for logging
if (e.OriginalState == System.Data.ConnectionState.Open ||
e.CurrentState != System.Data.ConnectionState.Open) {
return;
}
int uid = _uidObtainer();
var conn = ((System.Data.Entity.Core.EntityClient.EntityConnection)sender).StoreConnection;
var cmd = conn.CreateCommand();
cmd.CommandText = "audit.SetContext";
cmd.CommandType = System.Data.CommandType.StoredProcedure;
cmd.Parameters.Add(new System.Data.SqlClient.SqlParameter("#DomainUserID", uid));
cmd.ExecuteNonQuery();
}
// etc etc...
In my MVC project, I'll have code that looks like this:
context = new Data.CoreContext(() => AppService.UserID());
(making use of a readily accessible method to pass as delegate, which in turn reads from HttpContext.Current.User)
This is all shaping up nicely, except one unknown:
I know that it's possible for a EF Context instance to span multiple logged in users as this lives as part of the IIS app pool and not per HttpContext
What I don't know is enough about connection pooling and how connections are opened/re-opened to be safe in knowing that for each time my StateChange handler runs, I'll actually be retrieving the new UserID from the delegate.
Said differently: is it possible for a single connection to be open and used over the span of two separate HttpContext instances? I believe yes, seeing as how there's nothing to enforce otherwise (at least not that I'm aware of).
What can I do to ensure that each connection is getting the current HttpContext?
(possibly pertinent notes: There's no UoW/Repository pattern outside of EF itself, and data contexts are generally instantiated once per controller)
I see: the one context per controller is generally incorrect. Instead I should be using one context per request, which (besides other advantages), ensures my scenario operates correctly as well.
I found this answer, which explains the reasoning behind it: One DbContext per web request... why?
And I found this answer, which explains quite succinctly how to implement via BeginRequest and EndRequest: One DbContext per request in ASP.NET MVC (without IOC container)
(code from second answer pasted below to prevent linkrot)
protected virtual void Application_BeginRequest()
{
HttpContext.Current.Items["_EntityContext"] = new EntityContext();
}
protected virtual void Application_EndRequest()
{
var entityContext = HttpContext.Current.Items["_EntityContext"] as EntityContext;
if (entityContext != null)
entityContext.Dispose();
}
And in your EntityContext class...
public class EntityContext
{
public static EntityContext Current
{
get { return HttpContext.Current.Items["_EntityContext"] as EntityContext; }
}
}
I'm currently working on a Windows Store app (for a school assignment), and I'm having trouble inserting data into my database which is stored in Azure. Whenever I attempt to insert data into the db, the MobileServiceInvalidOperationException gets thrown. My code is as follows:
In my model class
class Division
{
public string Id {get; set;}
[JsonProperty(PropertyName = "divisionTitle")]
public string DivisionTitle {get; set;}
}
And the relevant code in my MainPage.xaml.cs file
private MobileServiceCollection<Division, Division> divisionItems;
private IMobileServiceTable<Division> divisionTable = App.MobileService.GetTable<Division>();
private async void InsertDivision(Division divisionItem)
{
// This code inserts a new division Item into the database.
// When the operation completes and Mobile Services has
// assigned an Id, the item is added to the collection
try
{
await divisionTable.InsertAsync(divisionItem);
divisionItems.Add(divisionItem);
}
/////////////////////////////////////////////////////////
// The MessageDialog that pops up when this exception //
// gets thrown is: //
// //
// Internal Server Error (HTTP 500) //
////////////////////////////////////////////////////////
catch (MobileServiceInvalidOperationException e)
{
MessageDialog errormsg = new MessageDialog(e.Message,
string.Format("{0} (HTTP {1})",
e.Response.ReasonPhrase,
(int)e.Response.StatusCode));
var ignoreAsyncOpResult = errormsg.ShowAsync();
}
}
private void DivisionButtonSave_Click(object sender, RoutedEventArgs e)
{
var DivisionItem = new Division
{
DivisionTitle = DivisionInput.Text
};
InsertDivision(DivisionItem);
}
I also added a script in the management portal:
function insert(item, user, request) {
if (item.DivisionTitle.length > 15) {
request.respond(statusCodes.BAD_REQUEST, 'Division title must be under 15 characters');
}
else {
request.execute();
}
}
Before making the changes above, I was having no trouble communicating with Azure from within the app and wasn't having any problems inserting data. It's only after editing the script in Azure (the default insert method is simply the request.execute() statement), and since I added the InsertDivision method (I was previously entering data into the db directly from the event handler with the command await App.MobileService.GetTable<Division>().InsertAsync(DivisionItem);) that this problem has started to occur. I've tried a couple of different things and nothing has worked. After looking at my code does anything stick out? Thanks in advance to anyone who can help.
In the request sent to the service, the property DivisionTitle is sent with the first letter in lower case (since you defined it as such with the JsonProperty attribute):
{"divisionTitle":"the actual title"}
On your script, you're trying to access the property item.DivisionTitle (which doesn't exist, JavaScript is case-sensitive), and then access a property (length) of this undefined value. That will cause an error in your script. If you either change the script to use the actual JSON name (item.divisionTitle.length > 15) or change the JsonProperty declaration in the client to send the property with the first letter in upper case, it should work.
By the way, if you go to the "logs" tab in the portal, you should see some error which explains why you're getting the internal server error.
I am creating a custom workflow that is - Triggered on the create of a record (Custom Activity).
I need to be able to access the data from the Custom Activity above within my Custom workflow but I am have a hard time finding a reference on how to get the information from the newly created record.
Any advice?
Thanks in advanced.
Edit:
public sealed class FeeInvoiceGenerator : CodeActivity
{
[Input("MyFee")]
[ReferenceTarget("fee")]
[RequiredArgument]
public InArgument<EntityReference> SomeFee { get; set; }
protected override void Execute(CodeActivityContext executionContext)
{
ITracingService tracingService = executionContext.GetExtension<ITracingService>();
try
{
tracingService.Trace("Creating Invoice for Fee");
WorkFlowHelper workFlowHelper = new WorkFlowHelper();
workFlowHelper.debugMessagesOn = true;
//creates connection info
InvoiceFeeHelper invoiceFeeHelper = new InvoiceFeeHelper();
invoiceFeeHelper.ConnectionInfo(workFlowHelper);
invoiceFeeHelper.CreatingConnection(workFlowHelper, executionContext);
//initialize other classes
FeeMaster feeMaster = new FeeMaster();
InvoiceMaster invoiceMaster = new InvoiceMaster();
InvoiceFeeMaster invoiceFeeMaster = new InvoiceFeeMaster();
EntityReference getFee = this.SomeFee.Get(executionContext);
String feeId = getFee.Id.ToString();
invoiceFeeMaster.CreateInvoiceFromFee(workFlowHelper, invoiceFeeHelper, invoiceMaster, feeMaster, feeId, executionContext);
}
catch (Exception ex)
{
throw new NotImplementedException("error occured" + ex.Message);
}
}
}
However, I am running into an issue where I cannot access the [Set Properties] within the workflow itself to assign the input. (at least this is what I have seen from other examples online and it is blank for me)
I have also tried to use:
IWorkflowContext workFlowContext = workFlowHelper.context.GetExtension<IWorkflowContext>();
Guid _feeRecordID = workFlowContext.PrimaryEntityId;
in order to get the records Id to no avail. The other portions of my custom workflow work, If I pass in a guid from a 'Fee'(the record I need to grab), everything works great.
Am I doing something wrong?
Second Edit:
I needed to restart IIS on the CRM server before it would recognize that there were inputs to be used.
Couple of ways you could do this:
Use an input parameter to pass all the data you need.
Use an input parameter to pass the ID of the record, and then use the IOrganizationService to retrieve the rest of the data.
Please see Custom Workflow Activities (Workflow Assemblies) for Microsoft Dynamics CRM for examples and further detail.
I am not sure what your WorkflowHelper does but you should not really have to use it.
// Create the context
var context = executionContext.GetExtension<IWorkflowContext>();
//primary entityid
var _feeRecordId = context.PrimaryEntityId
I basically have created a class which when a user logs into a website it then queries the database and stores some settings in a List (So I have key/pair values).
The reason for this is because I want to always be able to access these settings without going to the database again.
I put these in a class and loop through the fields via a SQL query and add them to the list.
How can I then access these variables from another part of the application? or is there a better way to do this? I'm talking server side and not really client side.
Here is an example of what I had at the moment:
public static void createSystemMetaData()
{
string constring = ConfigurationManager.ConnectionStrings["Test"].ConnectionString;
SqlConnection sql = new SqlConnection(constring);
sql.Open();
SqlCommand systemMetaData = new SqlCommand("SELECT * FROM SD_TABLES", sql);
//Set Modules
using (SqlDataReader systemMetaDataReader = systemMetaData.ExecuteReader())
{
while (systemMetaDataReader.Read())
{
var name = systemMetaDataReader.GetOrdinal("Sequence").ToString();
var value = systemMetaDataReader.GetOrdinal("Property").ToString();
var Modules = new List<KeyValuePair<string, string>>();
Modules.Add(new KeyValuePair<string, string>(name, value));
}
}
}
Thanks
Any static properties of a class will be preserved for the lifetime of the application pool, assuming you're using ASP.NET under IIS.
So a very simple class might look like:
public static class MyConfigClass
{
public static Lazy<Something> MyConfig = new Lazy<Something>(() => GetSomethings());
public static Something GetSomethings()
{
// this will only be called once in your web application
}
}
You can then consume this by simply calling
MyConfigClass.MyConfig.Value
For less users you can go with the SessionState as Bob suggested,however with more users you might need to move to a state server or load it from Data Base each time.
As others have pointed out, the risk of holding these values in global memory is that the values might change. Also, global variables are a bad design decision as you can end up with various parts of your application reading and writing to these values, which makes debugging problems harder than it need be.
A commonly adopted solution is to wrap your database access inside a facade class. This class can then cache the values if you wish to avoid hitting the database for each request. In addition, as changes are routed through the facade too, it knows when the data has changed and can empty its cache (forcing a database re-read) when this occurs. As an added bonus, it becomes possible to mock the facade in order to test code without touching the database (database access is notoriously difficult to unit test).
From the looks of things you are using universal values irrespective of users so an SqlCacheDependency would be useful here:
Make sure you setup a database dependency in web.config for the name Test
public static class CacheData {
public static List<KeyValuePair<string,string>> GetData() {
var cache = System.Web.HttpContext.Current.Cache;
SqlCacheDependency SqlDep = null;
var modules = Cache["Modules"] as List<KeyValuePair<string,string>>;
if (modules == null) {
// Because of possible exceptions thrown when this
// code runs, use Try...Catch...Finally syntax.
try {
// Instantiate SqlDep using the SqlCacheDependency constructor.
SqlDep = new SqlCacheDependency("Test", "SD_TABLES");
}
// Handle the DatabaseNotEnabledForNotificationException with
// a call to the SqlCacheDependencyAdmin.EnableNotifications method.
catch (DatabaseNotEnabledForNotificationException exDBDis) {
SqlCacheDependencyAdmin.EnableNotifications("Test");
}
// Handle the TableNotEnabledForNotificationException with
// a call to the SqlCacheDependencyAdmin.EnableTableForNotifications method.
catch (TableNotEnabledForNotificationException exTabDis) {
SqlCacheDependencyAdmin.EnableTableForNotifications("Test", "SD_TABLES");
}
finally {
// Assign a value to modules here before calling the next line
Cache.Insert("Modules", modules, SqlDep);
}
}
return modules;
}