Caching Results in a Static Variable - asp.net

I'm using a Linq query to retrieve entities from an SQL server using the Entity Framework. When I update an entitiy, the EF is caching the result. I suspect this is because the ObjectContext is in a static variable (below). The only way to refresh the data using my code below is to call a method and set _db to null when there might be stale data displayed (Eg: in a GridView). Is there a way to just prevent it from caching, or to add some sort of end request handler to call this method on my data layer instead of needing to detect when there may be stale data displayed?
private static ServiceEntities _db;
protected static ServiceEntitiesDb
{
get
{
if (_db == null)
{
_db = new ServiceEntities();
_db.Contacts.MergeOption = MergeOption.OverwriteChanges; // failed
}
return _db;
}
}
public static IEnumerable<Contact> GetContactsByName(string name) {
var items = Db.Contacts;
var filteredName = items.Where(i => (i.Name??string.Empty).IndexOf(name) >=0);
return filteredName;
}

The slightly verbose solution (which I wanted to avoid) is to wrap it in a using block. Ie:
public static IEnumerable<Contact> GetContactsByName(string name) {
var items = Db.Contacts;
var filteredName = items.Where(i => (i.Name??string.Empty).IndexOf(name) >=0);
return filteredName;
}
Becomes
public static IEnumerable<Contact> GetContactsByName(string name) {
using (var db = new SomeContext()) {
var items = db.Contacts;
var filteredName = items.Where(i => (i.Name??string.Empty).IndexOf(name) >=0);
return filteredName;
}
}

Related

System.InvalidOperationException while dynamically enumerating and retrieving .NET6 Entity Framework DbContext DbSet<...>s instances' records

The subject runtime error's location is inside the following code. Please advise how to fix this issue.
using (var ctx = new MyDbContext())
{
foreach (var entityInfo in ctx.GetEntitiesInfo())
System.Console.WriteLine($"{entityInfo.Index}. EntityName = {entityInfo.Name}, Records count = {entityInfo.RecordCount}");
}
public static class Extensions
{
public readonly record struct EntityInfo (int Index, string? Name, int RecordCount);
public static IEnumerable<EntityInfo> GetEntitiesInfo(this MyDbContext context)
{
var dbSetProperties = context.GetDbSetProperties();
var dbSets = dbSetProperties.Select(x => x.GetValue(context, null)).ToList();
var index = 0;
foreach (IQueryable? dbSet in dbSets)
{
++index;
// Runtime Error happens on next code line on second iteration
// when index = 2:
//
// System.InvalidOperationException:
// 'There is already an open DataReader associated with
// this Connection which must be closed first
//-
dbSet.Load();
var items = new List<object>();
var enumerator = dbSet?.GetEnumerator();
while (enumerator?.MoveNext() == true)
{
var item = enumerator.Current;
items.Add(item);
}
yield return new EntityInfo(index, dbSet?.ElementType?.ToString(), items.Count);
}
}
public static IEnumerable<PropertyInfo> GetDbSetProperties(this MyDbContext context)
{
foreach (var property in context.GetType().GetProperties())
if (property?.PropertyType?.FullName?
.Contains("Microsoft.EntityFrameworkCore.DbSet`") == true)
yield return property;
}
}
[Update]
Actually I wanted to develop a generic code to get all DbSet(s) of a given DbContext. Here it's based on Ivan Stoev's answer to this topic:
public static IEnumerable<IQueryable<object>> GetDbSets(this MyDbContext context)
{
var dbSetProperties = context.GetDbSetTypeProperties();
return dbSetProperties.Select(x => x.GetValue(context, null))
.Cast<IQueryable<object>>();
}
public static IEnumerable<PropertyInfo> GetDbSetTypeProperties(this MyDbContext context)
{
foreach (var property in context.GetType().GetProperties())
if (property?.PropertyType?.FullName?
.Contains("Microsoft.EntityFrameworkCore.DbSet`") == true)
yield return property;
}
Please advise if the above code can be even more simplified/streamlined.
[Update 2]
GetDbSets() method can be simplified this way:
public static IEnumerable<IQueryable<object>> GetDbSets(this MyDbContext context)
{
return context
.GetType()
.GetRuntimeProperties()
.Where(x => x.PropertyType?.FullName?.Contains("Microsoft.EntityFrameworkCore.DbSet`") == true)
.Select(x => x.GetValue(context, null))
.Cast<IQueryable<object>>();
}
but is there any other ways to enumerate all DbSet(s) of a given DbContext?
There are a lot of flaws in that code snippet, but the concrete problem in question is caused by the lack of disposal of the IEnumerator returned by the IEnumerable.GetEnumerator() call.
I know the non generic IEnumerator does not implement IDisposable, but that was a miss, and the generic IEnumerator<T> added later normally implements both IEnumerator and IDisposable.
So you have to either account for that and modify the code similar to this:
var items = new List<object>();
var enumerator = dbSet?.GetEnumerator();
try
{
while (enumerator?.MoveNext() == true)
{
var item = enumerator.Current;
items.Add(item);
}
}
finally
{
(enumerator as IDisposable)?.Dispose();
}
or better let the C# foreach do that for you (writing behind the scenes a code similar to the above):
var items = new List<object>();
foreach (var item in dbSet)
items.Add(item);
Note that DbSet<T> type properties are the equivalent of Set<T>() method and are initialized by EF Core at the DbContext instance creation, so neither they nor their content is null, hence all the ?. operators are not needed.
Also calling the Load method is not needed since all it does is to enumerate the db set without storing items in a list (basically foreach with empty body).
Anyway, both previous code snippets will fix the original issue. But there is even better ways. Since all entities are required to be classes (reference types), each db set can be cast to IQueryable<object>
foreach (IQueryable<object> dbSet in dbSets)
which offers some additional benefits.
First, the original code could be fixed just by adding single using statement:
var items = new List<object>();
using var enumerator = dbSet?.GetEnumerator();
while (enumerator?.MoveNext() == true)
{
var item = enumerator.Current;
items.Add(item);
}
It can be replaced with foreach as before
var items = new List<object>();
foreach (var item in dbSet)
items.Add(item);
but now you have access to many Enumerable (and Queryable and EF Core specific) extension methods, so you can replace all the iteration code with simple
var items = dbSet.ToList();
And in case you just need the record count, retrieve it with server side query without loading all the data in memory
var itemsCount = dbSet.Count();
yield return new EntityInfo(index, dbSet.ElementType.ToString(), itemsCount);

AspNetIdentityDocumentDB and Cross partition query is required but disabled

I have an app that uses CosmosDb as the database and using AspNetIdentityDocument. When I call var result = await _signInManager.PasswordSignInAsync(model.UserName, model.Password, model.RememberMe, false), i get the error Cross partition query is required but disabled. Please set x-ms-documentdb-query-enablecrosspartition to true, specify x-ms-documentdb-partitionkey
void InitializeDocumentClient(DocumentClient client) code attempts to create the container if not there. It works for the creating the container on my CossmosDb emultated store but fails on the Azure store requiring a partition key! My app works on the emulated store!
Program.cs
builder.Services.AddDefaultDocumentClientForIdentity(
builder.Configuration.GetValue<Uri>("DocumentDbClient:EndpointUri"),
builder.Configuration.GetValue<string>("DocumentDbClient:AuthorizationKey"),
afterCreation: InitializeDocumentClient);
builder.Services.AddIdentity<ApplicationUser, DocumentDbIdentityRole>()
.AddDocumentDbStores(options =>
{
options.UserStoreDocumentCollection = "AspNetIdentity";
options.Database = "RNPbooking";
})
.AddDefaultTokenProviders();
void InitializeDocumentClient(DocumentClient client)
{
try
{
var db = client.ReadDatabaseAsync(UriFactory.CreateDatabaseUri("RNPbooking")).Result;
}
catch (AggregateException ae)
{
ae.Handle(ex =>
{
if (ex.GetType() == typeof(DocumentClientException) && ((DocumentClientException)ex).StatusCode == HttpStatusCode.NotFound)
{
var db = client.CreateDatabaseAsync(new Microsoft.Azure.Documents.Database() { Id = "RNPbooking" }).Result;
return true;
}
return false;
});
}
try
{
var collection = client.ReadDocumentCollectionAsync(UriFactory.CreateDocumentCollectionUri("RNPbooking", "AspNetIdentity")).Result;
}
catch (AggregateException ae)
{
ae.Handle(ex =>
{
if (ex.GetType() == typeof(DocumentClientException) && ((DocumentClientException)ex).StatusCode == HttpStatusCode.NotFound)
{
DocumentCollection collection = new DocumentCollection()
{
Id = "AspNetIdentity"
};
collection = client.CreateDocumentCollectionAsync(UriFactory.CreateDatabaseUri("RNPbooking"),collection).Result;
return true;
}
return false;
});
}
}
Controller
[Authorize(Roles = "Admin)]
public class AdminController : Controller
{
private readonly UserManager<ApplicationUser> _userManager;
private readonly SignInManager<ApplicationUser> _signInManager;
public CosmosClient _client;
public AdminController(
UserManager<ApplicationUser> userManager,
SignInManager<ApplicationUser> signInManager,
)
{
_userManager = userManager;
_signInManager = signInManager;
}
You need to fill in CreateDocumentCollectionUri method with FeedOptions object as a parameter
UriFactory.CreateDocumentCollectionUri(DatabaseId, CollectionId),new FeedOptions { EnableCrossPartitionQuery=true})
UPDATED: From the code examples, you seem to be using this library https://github.com/codekoenig/AspNetCore.Identity.DocumentDb, AspNetCore.Identity.DocumentDb.
This error means the library you are using is performing a Document Query in their code at some point, it is not related to the creation of the Database or Collection.
The library code must be using CreateDocumentQuery somewhere, that code is missing:
new FeedOptions { EnableCrossPartitionQuery = true };
If you search their code base, you will see multiple scenarios like that: https://github.com/codekoenig/AspNetCore.Identity.DocumentDb/search?q=CreateDocumentQuery
Because this code is out of your control, you should try and contact the owner to see if this is a fix they can do on their end. The code for the library doesn't seem to have been updated in several years, so maybe this library is not maintained?

Write to entity framework database within Task.Factory.StartNew

How can I save something to my database in the entity framework within a separate thread?
What I want to do could look like that, but obviously doesn't work like that:
public class ThingsHandler : IHttpHandler
{
private DatabaseContext db = new DatabaseContext();
private void ProcessThing()
{
System.Threading.Tasks.Task.Factory.StartNew(() =>
{
Thing dbThing = db.Things.Where(...).AsEnumerable().LastOrDefault();
dbThing.some_property = true;
db.SaveChanges();
}
}
I hope, I was able to make clear, what I would like to achieve. But what do I need to do?
If you want to stay with task, you should place context initialization inside task:
System.Threading.Tasks.Task.Factory.StartNew(() =>
{
var db = new DatabaseContext();
Thing dbThing = db.Things.Where(...).AsEnumerable().LastOrDefault();
dbThing.some_property = true;
db.SaveChanges();
}
But if you use EF6, you might want to use Async methods:
private async void ProcessThing()
{
Thing dbThing = await db.Things.Where(...).AsEnumerable().LastOrDefaultAsync();
dbThing.some_property = true;
await db.SaveChangesAsync();
}

Using one session per request, how to handle updating child objects

I'm having some serious issues with Fluent Nhibernate in my ASP.NET WebForms app when trying to modify a child object and then saving the parent object.
My solution is currently made of 2 projects :
Core : A class library where all entities & repositories classes are located
Website : The ASP.NET 4.5 WebForms application
Here is my simple mapping for my Employee object:
public class EmployeeMap : ClassMap<Employee>
{
public EmployeeMap()
{
Id(x => x.Id).GeneratedBy.Identity();
Map(x => x.DateCreated);
Map(x => x.Username);
Map(x => x.FirstName);
Map(x => x.LastName);
HasMany(x => x.TimeEntries).Inverse().Cascade.All().KeyColumn("Employee_id");
}
}
Here is my my mapping for the TimeEntry object:
public class TimeEntryMap : ClassMap<TimeEntry>
{
public TimeEntryMap()
{
Id(x => x.Id).GeneratedBy.Identity();
Map(x => x.DateCreated);
Map(x => x.Date);
Map(x => x.Length);
References(x => x.Employee).Column("Employee_id").Not.Nullable();
}
}
As stated in the title, i'm using one session per request in my web app, using this code in Gobal.asax:
public static ISessionFactory SessionFactory = Core.SessionFactoryManager.CreateSessionFactory();
public static ISession CurrentSession
{
get { return (ISession)HttpContext.Current.Items["current.session"]; }
set { HttpContext.Current.Items["current.session"] = value; }
}
protected Global()
{
BeginRequest += delegate
{
System.Diagnostics.Debug.WriteLine("New Session");
CurrentSession = SessionFactory.OpenSession();
};
EndRequest += delegate
{
if (CurrentSession != null)
CurrentSession.Dispose();
};
}
Also, here is my SessionFactoryManager class:
public class SessionFactoryManager
{
public static ISession CurrentSession;
public static ISessionFactory CreateSessionFactory()
{
return Fluently.Configure()
.Database(MsSqlConfiguration.MsSql2008.ConnectionString(c => c.FromConnectionStringWithKey("Website.Properties.Settings.WebSiteConnString")))
.Mappings(m => m
.FluentMappings.AddFromAssembly(Assembly.GetExecutingAssembly()))
.ExposeConfiguration(cfg => new SchemaUpdate(cfg).Execute(false, true))
.BuildSessionFactory();
}
public static ISession GetSession()
{
return (ISession)HttpContext.Current.Items["current.session"];
}
}
Here is one of my repository class, the one i use to handle the Employee's object data operations:
public class EmployeeRepository<T> : IRepository<T> where T : Employee
{
private readonly ISession _session;
public EmployeeRepository(ISession session)
{
_session = session;
}
public T GetById(int id)
{
T result = null;
using (ITransaction tx = _session.BeginTransaction())
{
result = _session.Get<T>(id);
tx.Commit();
}
return result;
}
public IList<T> GetAll()
{
IList<T> result = null;
using (ITransaction tx = _session.BeginTransaction())
{
result = _session.Query<T>().ToList();
tx.Commit();
}
return result;
}
public bool Save(T item)
{
var result = false;
using (ITransaction tx = _session.BeginTransaction())
{
_session.SaveOrUpdate(item);
tx.Commit();
result = true;
}
return result;
}
public bool Delete(T item)
{
var result = false;
using (ITransaction tx = _session.BeginTransaction())
{
_session.Delete(_session.Load(typeof (T), item.Id));
tx.Commit();
result = true;
}
return result;
}
public int Count()
{
var result = 0;
using (ITransaction tx = _session.BeginTransaction())
{
result = _session.Query<T>().Count();
tx.Commit();
}
return result;
}
}
Now, here is my problem. When i'm trying to insert Employee(s), everything is fine. Updating is also perfect... well, as long as i'm not updating one of the TimeEntry object referenced in the "TimeEntries" property of Employee...
Here is where an exception is raised (in a ASPX file of the web project):
var emp = new Employee(1);
foreach (var timeEntry in emp.TimeEntries)
{
timeEntry.Length += 1;
}
emp.Save();
Here is the exception that is raised:
[NonUniqueObjectException: a different object with the same identifier
value was already associated with the session: 1, of entity:
Core.Entities.Employee]
Basically, whenever I try to
Load an employee and
Modify one of the saved TimeEntry, I get that exception.
FYI, I tried replacing the SaveOrUpdate() in the repository for Merge(). It did an excellent job, but when creating an object using Merge(), my object never gets it's Id set.
I also tried creating and flushing the ISession in each function of my repository. It made no sense because as soon as i was trying to load the TimeEntries property of an Employee, an exception was raised, saying the object could not be lazy-loaded as the ISession was closed...
I'm at lost and would appreciate some help. Any suggestion for my repository is also welcome, as i'm quite new to this.
Thanks you guys!
This code
var emp = new Employee(1);
foreach (var timeEntry in emp.TimeEntries)
{
timeEntry.Length += 1;
}
emp.Save();
is creating a new Employee object, presumable with an ID of 1 passed through the constructor. You should be loading the Employee from the database, and your Employee object should not allow the ID to be set since you are using an identity column. Also, a new Employee would not have any TimeEntries and the error message clearly points to an Employee instance as the problem.
I'm not a fan of transactions inside repositories and I'm really not a fan of generic repositories. Why is your EmployeeRepository a generic? Shouldn't it be
public class EmployeeRepository : IRepository<Employee>
I think your code should look something like:
var repository = new EmployeeRepository(session);
var emp = repository.GetById(1);
foreach (var timeEntry in emp.TimeEntries)
{
timeEntry.Length += 1;
}
repository.Save(emp);
Personally I prefer to work directly with the ISession:
using (var txn = _session.BeginTransaction())
{
var emp = _session.Get<Employee>(1);
foreach (var timeEntry in emp.TimeEntries)
{
timeEntry.Length += 1;
}
txn.Commit();
}
This StackOverflow Answer gives an excellent description of using merge.
But...
I believe that you are facing issues with setting up a correct session pattern for your application.
I you suggest to take a look at session-per-request pattern
where in you create a single NHibernate session object per request. the session is opened when the request is received and closed/flushed on generating a response.
Also make sure that instead of using SessionFactory.OpenSession() to get a session try using SessionFactory.GetCurrentSession() which puts the onus on NHibernate to return you the current correct session.
I hope this pushes you in the right direction.

Any reason why this LINQ to SQL update query is not working . .

Somehow this update code is not working:
Here is my Controller code:
private UserRepository repo = new UserRepository();
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Edit(int id, UserInfo user_)
{
try
{
repo.UpdateUser(user_);
return RedirectToAction("Index");
}
catch
{
return View();
}
}
Here is the repo code that is used above (UserRepository)
private UsersDataContext db = new UsersDataContext();
public void UpdateUser(UserInfo user_)
{
UserInfo origUser = GetUser(user_.Id);
origUser.First = user_.First;
origUser.Last = user_.Last;
origUser.City = user_.City;
origUser.Country = user_.Country;
origUser.State = user_.State;
origUser.Street_Address = user_.Street_Address;
db.SubmitChanges();
}
public UserInfo GetUser(int id_)
{
return db.UserInfos.SingleOrDefault(d => d.Id == id_);
}
EDIT:
Note that when debugging everything is running fine (no exceptions) but when it redirects back to Index the data has not been updated when the changes from the update.
i just changed the userrepository to the following:
private UsersDataContext db = new UsersDataContext();
public void UpdateUser(UserInfo user_)
{
UserInfo origUser = db.UserInfos.SingleOrDefault(d => d.Id == id_);
origUser.First = user_.First;
origUser.Last = user_.Last;
origUser.City = user_.City;
origUser.Country = user_.Country;
origUser.State = user_.State;
origUser.Street_Address = user_.Street_Address;
db.SubmitChanges();
}
so all i did was move the GetUser() method inline and it worked.
It might have been a red herring and it was just a caching issue . .
You don't mention how you've defined UserInfo, is it a struct or a class?
If it's a struct, returning it from GetUser will create a new object and thus you will not update the database object, only a local copy of it.
Moving the GetUser inline avoid this temp copy creation and that's likely why it's working.
afaik you could do something like
public void GetUser(int id_, out UserInfo user_)
{
user_ = db.UserInfos.SingleOrDefault(d => d.Id == id_);
}
You would then call it like this
public void UpdateUser(UserInfo user_)
{
UserInfo origUser;
GetUser(user_.Id, out origUser);
origUser.First = user_.First;
origUser.Last = user_.Last;
origUser.City = user_.City;
origUser.Country = user_.Country;
origUser.State = user_.State;
origUser.Street_Address = user_.Street_Address;
db.SubmitChanges();
}
Maybe you disabled Object Tracking?

Resources