Is there a way to look through the cache for all objects in the cache? I'm dynamically creating objects and I need to periodically go through the list to purge out objects I'm no longer using.
var keysToClear = (from System.Collections.DictionaryEntry dict in HttpContext.Cache
let key = dict.Key.ToString()
where key.StartsWith("Something_")
select key).ToList();
foreach (var key in keysToClear)
{
HttpContext.Cache.Remove(key);
}
You can enumerate through the objects:
System.Web.HttpContext.Current.Cache.GetEnumerator()
Yes, you can either index based on the cache key, or you you can iterate over the contents:
For Each c In Cache
' Do something with c
Next
' Pardon my VB syntax if it's wrong
Here is a VB function to iterate through the Cache and return a DataTable representation.
Private Function CreateTableFromHash() As DataTable
Dim dtSource As DataTable = New DataTable
dtSource.Columns.Add("Key", System.Type.GetType("System.String"))
dtSource.Columns.Add("Value", System.Type.GetType("System.String"))
Dim htCache As Hashtable = CacheManager.GetHash()
Dim item As DictionaryEntry
If Not IsNothing(htCache) Then
For Each item In htCache
dtSource.Rows.Add(New Object() {item.Key.ToString, item.Value.ToString})
Next
End If
Return dtSource
End Function
This may be a bit late, but I used the following code to easily iterate over all cache items and perform some custom logic for removal of cache items that contain a certain string in their names.
I have provided both versions of code in VB.Net as well as C#.
VB.Net Version
Dim cacheItemsToRemove As New List(Of String)()
Dim key As String = Nothing
'loop through all cache items
For Each c As DictionaryEntry In System.Web.HttpContext.Current.Cache
key = DirectCast(c.Key, String)
If key.Contains("prg") Then
cacheItemsToRemove.Add(key)
End If
Next
'remove the selected cache items
For Each k As var In cacheItemsToRemove
System.Web.HttpContext.Current.Cache.Remove(k)
Next
C# Version
List<string> cacheItemsToRemove = new List<string>();
string key = null;
//loop through all cache items
foreach (DictionaryEntry c in System.Web.HttpContext.Current.Cache)
{
key = (string)c.Key;
if (key.Contains("prg"))
{
cacheItemsToRemove.Add(key);
}
}
//remove the selected cache items
foreach (var k in cacheItemsToRemove)
{
System.Web.HttpContext.Current.Cache.Remove(k);
}
Since you potentially want to be removing items from the Cache object, it is not terribly convenient to iterate over it (as an IEnumerable), since this doesn't allow removal during the iteration process. However, given that you cannot access items by index, it is the only solution.
A bit of LINQ can however simplify the problem. Try something like the following:
var cache = HttpContext.Current.Cache;
var itemsToRemove = cache.Where(item => myPredicateHere).ToArray();
foreach (var item in itemsToRemove)
cache.Remove(itemsToRemove.Key);
Note that each item in the iteration is of type DictionaryEntry.
Jeff, you should really look up dependencies for your cached items. That's the proper way of doing this. Logically group your cached data (items) and setup dependencies for your groups. This way when you need to expire the entire group you touch such common dependency and they're all gone.
I'm not sure I understand the List of Object part.
Related
I have a dropbox webhook hitting my page. I need to grab the list of users in the response which I will process later on a separate thread. I can see the users in the data variable but I don't know how to extract the list of users in the object. Basically I want to populate an array of users which I can loop through and do some other processing. Hope this makes sense.
This is what the object looks like:
{
"delta": {
"users": [
12345678,
23456789,
...
]
}
}
This is the code I tried and like I say, I can see the string in data:
Dim strJSON = [String].Empty
Context.Request.InputStream.Position = 0
Using inputStream = New StreamReader(Context.Request.InputStream)
strJSON = inputStream.ReadToEnd()
End Using
Dim javaScriptSerializer As New JavaScriptSerializer()
Dim data As Object = javaScriptSerializer.Deserialize(strJSON, GetType(Object))
I would like an array of the users. Hope you can help.
Just make some classes for your response data:
Class Data
Public Property delta As Delta
End Class
Class Delta
' If you would rather have a list you can declare this As List(Of Integer) instead
Public Property users As Integer()
End Class
You can then deserialize directly into the classes:
Dim data As Data = javaScriptSerializer.Deserialize(Of Data)(strJSON)
From there you can work with your data easily enough:
For Each user As Integer In data.delta.users
Console.WriteLine(user)
Next
I am working with VB.Net. I have resolved a behavior where the code returns with an error of "Cannot access a disposed object. Object name: 'DataContext accessed after Dispose.'."
My problem : I simply don't understand why it works !
Here is my code when happens the error :
Public Function Element_Import(ByVal id_element As Integer) As elements
Using db As New GlobalDataContext(MyConnexion)
Return (From element In db.elements
Select element
Where element.id_element = id_element).First
End Using
End Function
Then I try to retreave the data :
Dim myElement As elements = _MyConnection.Element_Import(id_element)
Dim myLocal As List(Of elements_localisation) = myElement.elements_localisation.ToList '-- ObjectDisposedException !
When I import my element then try to access sub-tables called "elements_localisation" and "elements_files" an ObjectDisposedException occures. That's fair as the DataContext is not available.
So I have done something different :
Public Function Element_ImportContext(ByVal id_element As Integer) As elements
Dim myElement As elements
Dim myElementLocalisation As New List(Of elements_localisation)
Dim myElementFiles As New List(Of elements_files)
Using db As New GlobalDataContext(MyConnexion)
myElement = (From element In db.elements
Select element
Where element.id_element = id_element).First
myElementLocalisation = myElement.elements_localisation.ToList
myElementFiles = myElement.elements_files.ToList
End Using
Return myElement
End Function
Then I try to retreave the data :
Dim myElement As elements = _MyConnection.Element_ImportContext(id_element)
Dim myLocal As List(Of elements_localisation) = myElement.elements_localisation.ToList '-- IT WORKS !
I really wanna understand what happened as, in both case, the DataContext is disposed.
Can anyone explain ?
When you write LINQ, you write a query to be executed against some data, that query is only executed when another part of the code needs the data.
In your case, you are returning the myElement variable because you have used .First(), which forces the query to be executed and the first item to be returned.
The properties elements_localisation and elements_files are likely to be virtual properties that are only loaded when you request them. (This is certainly the case with Entity Framework, I'm not sure what you're using here).
Your program will try to access the virtual property which will then go off to the data context to get the next bit of data you requested, but in your case the data context is disposed.
Your second approach works because you have used ToList() on the virtual properties, which forces the data to be retrieved then and there, before your data context has been disposed.
I'm running an LDAP query that returns multiple entries and stores them inside a SearchResultCollection. I'm iterating over the SearchResultCollection like so:
// results is my SearchResultCollection object
foreach (SearchResult sr in results)
{
... do things to each SearchResult in here ...
}
This seems like the most logical way to do this, but loop is incredibly slow. When I step through the loop with the debugger, I find that it's the very first step of initializing the foreach loop that takes the time - the actual iterations are instantaneous.
In addition, when I view the contents of the SearchResultCollection while debugging, the watch takes just as long to load the contents of the variable.
I have a theory that the SearchResultCollection doesn't actually contain complete SearchResult objects, but rather references to entries in the Active Directory server that are then individually fetched when I iterate over the SearchResultCollection object. Can anyone confirm this theory? And is there a better (faster) way to fetch a set of LDAP entries?
I suffered from the same problem and the methodology in the post below was much quicker then my own:
http://msdn.microsoft.com/en-us/library/ms180881(v=vs.80).aspx
... to answer you question yes it seems as if you try to process the entries directly while still in the loop by using something like:
If Not UserAccount.GetDirectoryEntry().Properties("sAMAccountName").Value Is Nothing Then sAMAccountName = UserAccount.Properties("sAMAccountName")(0).ToString()
... this has a drastic impact on performance, you can get around this by adding the collection to a Dictionary within the loop and then processing the dictionary:
Dim searchResult As SearchResult
Dim dictLatestLogonDatesTemp As New Dictionary(Of String, Date)
SearchResults1 = mySearcher.FindAll()
For Each searchResult In SearchResults1
Dim propertyKey,sAMAccountName As String
Dim dteLastLogonDate As Date = Nothing
For Each propertyKey In searchResult.Properties.PropertyNames
Dim valueCollection As ResultPropertyValueCollection = searchResult.Properties(propertyKey)
For Each propertyValue As Object In valueCollection
If LCase(propertyKey) = LCase("sAMAccountName") Then sAMAccountName = propertyValue
If LCase(propertyKey) = LCase("lastLogon") Then dteLastLogonDate = Date.FromFileTime(propertyValue)
Next propertyValue
Next propertyKey
If sAMAccountName <> Nothing Then dictLatestLogonDatesTemp.Add(sAMAccountName, dteLastLogonDate)
Next searchResult
It is a bit restrictive bacause a dictionary ony has two entries but you can comma seperate other values or use a dictionary of values in the dictionary:
Dim tempDictionary As Dictionary(Of String, Dictionary(Of String, String))
Hope this helps someone!
I would like to submit that adding them to a datatable also works very well and allows for more properties than a dictionary. I went from iterating 2000 users in the searchresultscollection taking almost 2 minutes to less that 1-2 seconds. Hope this helps others.
Private Sub getAllUsers()
Dim r As SearchResultCollection
Dim de As DirectoryEntry = New DirectoryEntry(GetCurrentDomainPath)
Dim ds As New DirectorySearcher(de)
ds.SearchScope = SearchScope.Subtree
ds.PropertiesToLoad.Add("name")
ds.PropertiesToLoad.Add("distinguishedName")
ds.PropertiesToLoad.Add("objectSID")
ds.Filter = "(&(objectCategory=person)(objectClass=user))" '(!userAccountControl:1.2.840.113556.1.4.803:=2) not disabled users
PleaseWait.Status("Loading Users...")
Application.DoEvents()
r = ds.FindAll()
Dim dt As New DataTable
dt.Columns.Add("Name")
dt.Columns.Add("SID")
For Each sr As SearchResult In r
Dim SID As New SecurityIdentifier(CType(sr.Properties("objectSID")(0), Byte()), 0)
dt.Rows.Add(sr.Properties("name")(0).ToString(), SID.ToString)
Next
With lstResults
lstResults.DataSource = dt
.DisplayMember = "name"
.ValueMember = "SID"
.Items.Sort()
End With
End Sub
There may be some ways to decrease the response time:
restrict the scope of the search
use a more restrictive search filter
use a base object closer to the object(s) being retrieved.
see also
LDAP: Mastering Search Filters
LDAP: Programming practices
LDAP: Search Best Practices
I'm new to Entity Framework and am expanding an existing codebase. I'm using jQuery to pass the needed info back to the server ajaxy style, so I can't use TryUpdateModel(). Here's the code:
<HttpPost()>
Function UpdateRoster() As JsonResult
Dim model As New Models.ViewModels.PlayerAdmin
Dim jsonString As String = Request.Form("json")
model = Deserialise(Of Models.ViewModels.PlayerAdmin)(jsonString)
For Each playerAdminPlayer As Models.ViewModels.PlayerAdminPlayer In model.Roster
Dim playerToTeam As New DAL.PlayersToTeam
Dim player As DAL.Player = PlayerAdminManager.GetPlayerById(playerAdminPlayer.PlayerId)
player.FirstName = playerAdminPlayer.FirstName
PlayerAdminManager.SaveChanges()
Next playerAdminPlayer
Dim playerAfter As DAL.Player = PlayerAdminManager.GetPlayerById(model.Roster.First.PlayerId)
Return Json(New With {.success = False, .message = playerAfter.FirstName})
End Function
Deserialise is a helper function that converts the incoming JSON string to a vb object.
Things seem to work fine in that player successfully loads from the DB and playerAdminPlayer is the correct object from the JSON string. However, when I call PlayerAdminManager.SaveChanges() (which just passes the call the db.SaveChanges() the result is always 0, even if there is a change (not sure if that is expected).
playerAfter was my attempt to see if changes were actually being saved. It seems to work correctly, in that playerAfter.FirstName is the newly updated first name.
PlayerAdminManager.GetPlayerById(integer) pulls from the DB, so I would think that, since changes are observed in playerAfter, that those changes were saved to the DB. However, when I reload the web page (which pulls from the DB), the old values are there.
Any ideas?
Here are some of the functions I mention:
Function GetPlayerById(ByVal Id As Integer) As DAL.Player
Return Container.Players.Where(Function(o) o.PlayerId = Id And o.IsVisible = True).SingleOrDefault
End Function
Sub SaveChanges()
Dim numberOfChanges As Integer = Container.SaveChanges()
Debug.WriteLine("No conflicts. " & numberOfChanges.ToString() & " updates saved.")
End Sub
EDIT
Container code:
Private _Container As DAL.LateralSportsContainer
Protected ReadOnly Property Container As DAL.LateralSportsContainer
Get
If _Container Is Nothing Then
Dim connStr As New System.Data.EntityClient.EntityConnectionStringBuilder
connStr.ProviderConnectionString = Web.Configuration.WebConfigurationManager.ConnectionStrings("ApplicationServices").ConnectionString
connStr.Metadata = "res://*/Lateral.csdl|res://*/Lateral.ssdl|res://*/Lateral.msl"
connStr.Provider = "System.Data.SqlClient"
_Container = New DAL.LateralSportsContainer(connStr.ConnectionString)
End If
Return _Container
End Get
End Property
Turns out I was using a non static (shared) Container. I had 2 Manager classes that both inherited from a BaseManager class were the Container was defined. I was executing the query command in one Manager and saving in another.
Doh!
I have a *.MDB database file, and I am wondering if it is possible or recommended to work against it using LINQ in C#. I am also wondering what some simple examples would look like.
I don't know a lot about LINQ, but my requirements for this task are pretty simple (I believe). The user will be passing me a file path to Microsoft Access MDB database and I would like to use LINQ to add rows to one of the tables within the database.
What you want is a LINQ to ODBC provider, or a LINQ to JET/OLEDB provider.
Out of the box, MS doesn't make one. There may be a 3rd party who does.
Actually I recently (today) discovered that you can access an Access database with LinqToSql. It must be in the 2002 or newer format, you will not be able to drag and drop the tables to your datacontext so either manually create the objects in your dbml or you can use SQL Server Migration for Access to move it to a sql server and then drag and drop all you want. When you want to actually create the context pass it an OleDbConnection. Use your standard Jet.OLEDB.4.0 connection string on the OleDbConnection and you are good to go. Not sure of the limitation this may incurr though. I just did a quick sample and did an OrderBy without issue.
I wrote a small sample program to test this out with David's answer. You'll need to make an access database and manually create the DBML for Linq-to-SQL, as you cannot drag 'n drop them.
Inserts fail, citing Missing semicolon (;) at end of SQL statement. but queries seem to work alright.
using System;
using System.Collections.Generic;
using System.Data.OleDb;
using System.IO;
using System.Linq;
using Linq2Access.Data;
namespace Linq2Access
{
class Program
{
static readonly string AppPath = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);
static readonly string DbPath = Path.Combine(AppPath, "Data", "database.accdb");
static readonly string DbConnString = #"Provider=Microsoft.ACE.OLEDB.12.0;Data Source='" + DbPath + "';Persist Security Info=False;";
static void Main(string[] args)
{
if (!File.Exists(DbPath))
throw new Exception("Database file does not exist!");
using (OleDbConnection connection = new OleDbConnection(DbConnString))
using (DataRepositoryDataContext db = new DataRepositoryDataContext(connection))
{
List<dbProject> projects = new List<dbProject>();
for (int i = 1; i <= 10; i++)
{
dbProject p = new dbProject() { Title = "Project #" + i };
for (int j = 1; j <= 10; j++)
{
dbTask t = new dbTask() { Title = "Task #" + (i * j) };
p.dbTasks.Add(t);
}
projects.Add(p);
}
try
{
//This will fail to submit
db.dbProjects.InsertAllOnSubmit(projects);
db.SubmitChanges();
Console.WriteLine("Write succeeded! {0} projects, {1} tasks inserted",
projects.Count,
projects.Sum(x => x.dbTasks.Count));
}
catch(Exception ex)
{
Console.WriteLine("Write FAILED. Details:");
Console.WriteLine(ex);
Console.WriteLine();
}
try
{
//However, if you create the items manually in Access they seem to query fine
var projectsFromDb = db.dbProjects.Where(x => x.Title.Contains("#1"))
.OrderBy(x => x.ProjectID)
.ToList();
Console.WriteLine("Query succeeded! {0} Projects, {1} Tasks",
projectsFromDb.Count,
projectsFromDb.Sum(x => x.dbTasks.Count));
}
catch (Exception ex)
{
Console.WriteLine("Query FAILED. Details:");
Console.WriteLine(ex);
Console.WriteLine();
}
Console.WriteLine();
Console.WriteLine("Press any key to continue...");
Console.ReadKey();
}
}
}
}
You can use a DataSet. There are linq extensions that will allow you to query the data with all that LINQ goodness we have become use to :)
eICATDataSet.ICSWSbuDataTable tbl = new eICATDataSet.ICSWSbuDataTable();
ICSWSbuTableAdapter ta = new ICSWSbuTableAdapter();
ta.Fill(tbl);
var res = tbl.Select(x => x.ProcedureDate.Year == 2010);
I have seen this question a lot and in several fora. I made a go at it and here is a complete answer for those who have been looking at it.
LinQ was not made for Access. However, many of the queries will work with Access, including delete procedure. So, according to me, there are only 2 crucial deficiencies when working with Access, which are:
not being able to save data.
not being able to drag and drop objects onto the dbml
Insert will fail with the error "missing semicolon (;)". This is because LinQ save procedure was made to save data and retrieve the primary key ID of the record saved in one go. We know that you cannot execute multiple SQL statements in Access, so that is the reason for that failure.
Update will fail with the error "record not found". An update procedure will of cause look for the record to be updated then update it. I cannot tell why it wouldn't find it, when normal LinQ query to find a record works fine.
Because there is so much benefit to use LinQ, I figured out how to work around the deficiency, while enjoy the other benefits throughout my application. This is how (NB: My codes are in VB.net, but you can convert if required):
Create the LinQ to SQL (.dbml) class to manage your LinQ against the access database, and a way to manager your save procedure. Below is the full procedures of what I created and I now work with LinQ to Access without any problems:
Add a DataGridView on a form. Add buttons for Add, Edit & Delete
Code to fill the grid:
Private Sub ResetForm()
Try
Using db As New AccessDataClassesDataContext(ACCCon)
Dim rows = (From row In db.AccountTypes
Where row.AccountTypeID > 1
Order By row.AccountTypeID Ascending
Select row).ToList()
Me.DataGridView1.DataSource = rows
End Using
Catch ex As Exception
MessageBox.Show("Error: " & vbCr & ex.ToString, "Data Error", MessageBoxButtons.OK)
End Try
End Sub
DetailForm
Code to set control values
Private Sub ResetForm()
Try
If _accountTypeID = 0 Then
Exit Sub
End If
Using db As New AccessDataClassesDataContext(ACCCon)
'Dim rows = (From row In db.AccountTypes
' Where row.AccountTypeID = _accountTypeID
' Order By row.AccountTypeID Ascending
' Select row.AccountTypeID, row.AccountType, row.LastUpdated).ToList()
Dim rows = (From row In db.AccountTypes
Where row.AccountTypeID = _accountTypeID
Select row).ToList()
For Each s In rows
Me.AccountTypeIDTextBox.Text = s.AccountTypeID
Me.myGuidTextBox.Text = s.myGuid
Me.AccountTypeTextBox.Text = s.AccountType
Me.AcHeadIDTextBox.Text = s.AcHeadID
Me.DescriptionTextBox.Text = s.Description
Me.LastUpdatedDateTimePicker.Value = s.LastUpdated
Next
End Using
Catch ex As Exception
End Try
End Sub
LinQToSQLClass
You will have to add the data objects to the dbml manually since you cannot drag and drop when using Access. Also note that you will have to set all the properties of the fields correctly in the properties windows. Several properties are not set when you add the fields.
Code to Save
Public Function SaveAccountType(Optional ByVal type As String =
"Close") As Boolean
Dim success As Boolean = False
Dim row As New AccountType
Using db As New AccessDataClassesDataContext(ACCCon)
If _accountTypeID > 0 Then
row = (From r In db.AccountTypes
Where r.AccountTypeID = _accountTypeID).ToList()(0)
If String.IsNullOrEmpty(row.AccountTypeID) Then
MessageBox.Show("Requested record not found", "Update Customer Error")
Return success
End If
End If
Try
With row
.myGuid = Me.myGuidTextBox.Text
.AccountType = Me.AccountTypeTextBox.Text
.Description = Me.DescriptionTextBox.Text
.AcHeadID = Me.AcHeadIDTextBox.Text
.LastUpdated = Date.Parse(Date.Now())
End With
If _accountTypeID = 0 Then db.AccountTypes.InsertOnSubmit(row)
db.SubmitChanges()
success = True
Catch ex As Exception
MessageBox.Show("Error saving to Customer: " & vbCr & ex.ToString, "Save Data Error")
End Try
End Using
Return success
End Function
Now replace these two lines:
If _accountTypeID = 0 Then db.AccountTypes.InsertOnSubmit(row)
db.SubmitChanges()
with something like this:
Dim cmd As IDbCommand
cmd = Me.Connection.CreateCommand()
cmd.Transaction = Me.Transaction
cmd.CommandText = query
If myGuid.Trim.Length < 36 Then myGuid = UCase(System.Guid.NewGuid.ToString())
cmd.Parameters.Add(New OleDbParameter("myGuid", row.myGuid))
cmd.Parameters.Add(New OleDbParameter("AccountType", row.AccountType))
cmd.Parameters.Add(New OleDbParameter("Description", row.Description))
cmd.Parameters.Add(New OleDbParameter("AcHeadID", row.AcHeadID))
cmd.Parameters.Add(New OleDbParameter("LastUpdated", Date.Now))
If AccountTypeID > 0 Then cmd.Parameters.Add(New OleDbParameter("AccountTypeID", row.AccountTypeID))
If Connection.State = ConnectionState.Closed Then Connection.Open()
result = cmd.ExecuteNonQuery()
cmd = Me.Connection.CreateCommand()
cmd.Transaction = Me.Transaction
cmd.CommandText = "SELECT ##IDENTITY"
result = Convert.ToInt32(cmd.ExecuteScalar())
The last part of the code above is what gets you the ID of the record saved. Personally, I usually make that an option, because I don't need it in most of the cases, so I don't need to add that overhead of fetching back data every time a record is saved, I am happy just to know a record was saved.
That is the overhead added to LinQ, which causes Insert to fail with Access. Is it really necessary to have it? I don't think so.
You may have noted that I normally put my Update and Insert procedures together, so that saves me time and has address both the Insert & Update procedures in one go.
Code for Delete:
Private Sub DelButton_Click(sender As Object, e As EventArgs) Handles DelButton.Click
Using db As New AccessDataClassesDataContext(ACCCon)
Dim AccountTypeID As Integer = Me.DataGridView1.CurrentRow.Cells(0).Value
Dim row = From r In db.AccountTypes Where r.AccountTypeID = AccountTypeID
For Each detail In row
db.AccountTypes.DeleteOnSubmit(detail)
Next
Try
db.SubmitChanges()
Catch ex As Exception
' Provide for exceptions.
MsgBox(ex)
End Try
End Using
End Sub
Now you can enjoy LinQ to Access! Happy coding :)
LINQ to SQL only works for SQL Server databases. What you need is the Microsoft Entity Framework. This makes object oriented access to your mdb. From this you can run LINQ queries.
http://msdn.microsoft.com/en-us/library/aa697427(vs.80).aspx