Using SQLDependency or SqlCacheDependency with Repository pattern - asp.net-mvc

I am building an ASP.NET MVC application using the Repository pattern. A typical method in my Repository is as follows
public IList<T> Select<T>(string cacheKey, string Sql, object filter) where T: new()
{
IList<T> items = MemoryCache.Default.Get(cacheKey) as IList<T>;
if (items == null || !items.Any())
{
items = Connection.Select<T>(Sql, filter);
MemoryCache.Default.Add(cacheKey, items, DateTime.Now.AddMinutes(120));
}
return items;
}
and it is being used as follows
IEnumerable<OSADCOL> osadcols = Repository.Select<OSADCOL>("OSADCOLS__TblId=" + Id, "TBLid = #id", new { id = Id });
In the above mentioned example OSADCOL is a model in my app and represents a Table with the same name in my Database. The Connection.Select function is ORMlite function. I don't use Entity Framework for performance issues. My problem is the following. I am currently cashing the result set for future relevance but I am doing it in a hard coded way. I am caching the result set for 2 hours. It is obvious that a proper implementation would be to discard my cashed data when the OSADCOL's table data changes. It seems that I have to use SQLDependency or SQLCacheDependency. The questions are the following:
How am I going to use SQLDependency or SQLCacheDependency with this Repository?
What is the actual difference between these 2?
It is mentioned in some forums that SQLDependency creates memory leaks? Is that true? And If yes is there an alternative approach?
Any Ideas?

Related

How to delete tenant completely?

I'm developing a multitenant application and would like to have to option to remove a tenant. This however seems to be less trivial than one would assume.
My goal is to delete all references to the tenant everywhere in the database. I understand that Tenant is Soft-Delete, but I since I don't want my database to fill up with old meaningless data I've tried disabling the soft-delete filter.
Here is some code that I've tried:
using (_unitOfWorkManager.Current.DisableFilter(AbpDataFilters.SoftDelete))
{
await TenantRepository.DeleteAsync(x => x.Id == tenantId);
}
This did not work. The tenant is marked as "IsDeleted" but not removed.
Then I figured that maybe it has something to do with UnitOfWork so I made sure no UnitOfWork was active and then manually controlled it:
using (var unitOfWork = _unitOfWorkManager.Begin())
{
// the codeblock above went here
unitOfWork.Complete();
}
This did not work, same result. And this is just the AbpTenant table. I'm also trying to delete from all other tables. For example AbpSettings and AbpLanguages. It's very unclear to me how to do that at all - the "managers" doesn't contain any Delete functions.
I tried creating IRepository for these entities but it does not work. The error reads
The type Abo.Configuration.Setting cannot be used as a type parameter TEntity in the generic type or method IRepository. There is no implicit reference conversion from Abp.Configuration.Setting to Abo.Domain.Entities.IEntity.
That leaves me with the option to use the DataContext directly:
using (EntityFramework.MyDbContext db = new EntityFramework.MyDbContext())
{
List<PermissionSetting> perms = await db.Permissions.Where(x => x.TenantId == tenantId).ToListAsync();
for (int i=0; i<perms.Count(); i++)
{
db.Permissions.Remove(perms[i]);
}
// I also tried deleting them in bulk at first
// ((DbSet<PermissionSetting>)db.Permissions).RemoveRange(db.Permissions.Where(x => x.TenantId == tenantId));
await db.SaveChangesAsync();
}
I tried that with and without UnitOfWork.
But it simply does not get deleted from the database. I'm getting no errors or Exceptions.
Why does it not get deleted? How can I delete it? Surely it must be possible?
since I don't want my database to fill up with old meaningless data I've tried disabling the soft-delete filter.
From the question on Disable SoftDelete for AbpUserRole:
protected override void CancelDeletionForSoftDelete(EntityEntry entry)
{
if (IsSoftDeleteFilterEnabled)
{
base.CancelDeletionForSoftDelete(entry);
}
}
The type Abo.Configuration.Setting cannot be used as a type parameter TEntity in the generic type or method IRepository. There is no implicit reference conversion from Abp.Configuration.Setting to Abo.Domain.Entities.IEntity.
Inject IRepository<Setting, long> instead of IRepository<Setting>.
That leaves me with the option to use the DataContext directly
...
But it simply does not get deleted from the database. I'm getting no errors or Exceptions.
From the documentation on Data Filters:
using (_unitOfWorkManager.Current.DisableFilter(AbpDataFilters.MayHaveTenant))
{
using (var db = new ...)
{
// ...
}
}
That said, there is no way to easily delete related tenant data completely. Consider writing SQL.

Calling Stored Procedure From Entity Framework that returns table with variable columns

I am using ASP.NET MVC 3 and EF 4.x
I have a procedure that returns a result set but the columns are not fixed (it may return 25 columns or may be 40 or 50).
How can I call this stored procedure from Entity Framework?
When I use function import it asks for an Entity. But I cannot select it as none of the columns is fixed.
Entity Framework is not the right tool for this. It is good at statically defined data structures, not at dynamic ones.
There are better tools for this. I would recommend Dapper, created by Marc Gravell. It's easy as pie. Just get the NuGet package and type something like
using Dapper;
using (var cnn = new SqlConnection(myConnectionString))
{
cnn.Open();
var p = new DynamicParameters();
p.Add("#params", "Id=21");
var results = cnn.Query(sql:"GetMyData",
param: p,
commandType: CommandType.StoredProcedure);
foreach(IDictionary<string, object> result in results)
{
// Do something here.
}
}
Query is a Dapper extension method, result is a DapperRow, which is a private class that implements IDictionary<string, object>, so you can just access your data as a dictionary per record.
Aside from being easy to use, it's lightning fast too.

Entity Framework CTP5 - Reading Multiple Record Sets From a Stored Procedure

In EF4, this was not easily possible. You either had to degrade to classic ADO.NET (DataReader), use ObjectContext.Translate or use the EFExtensions project.
Has this been implemented off the shelf in EF CTP5?
If not, what is the recommended way of doing this?
Do we have to cast the DbContext<T> as an IObjectContextAdapter and access the underlying ObjectContext in order to get to this method?
Can someone point me to a good article on doing this with EF CTP5?
So i got this working, here's what i have:
internal SomeInternalPOCOWrapper FindXXX(string xxx)
{
Condition.Requires(xxx).IsNotNullOrEmpty();
var someInternalPokey = new SomeInternalPOCOWrapper();
var ctx = (this as IObjectContextAdapter).ObjectContext;
var con = new SqlConnection("xxxxx");
{
con.Open();
DbCommand cmd = con.CreateCommand();
cmd.CommandText = "exec dbo.usp_XXX #xxxx";
cmd.Parameters.Add(new SqlParameter("xxxx", xxx));
using (var rdr = cmd.ExecuteReader())
{
// -- RESULT SET #1
someInternalPokey.Prop1 = ctx.Translate<InternalPoco1>(rdr);
// -- RESULT SET #2
rdr.NextResult();
someInternalPokey.Prop2 = ctx.Translate<InternalPoco2>(rdr);
// -- RESULT SET #3
rdr.NextResult();
someInternalPokey.Prop3 = ctx.Translate<InternalPoco3>(rdr);
// RESULT SET #4
rdr.NextResult();
someInternalPokey.Prop4 = ctx.Translate<InternalPoco4>(rdr);
}
con.Close();
}
return someInternalPokey;
}
Essentially, it's basically like classic ADO.NET. You read the DbReader, advance to the next result set, etc.
But at least we have the Translate method which seemingly does a left-to-right between the result set fields and the supplied entity.
Note the method is internal.
My Repository calls this method, then hydrates the DTO into my domain objects.
I'm not 100% happy with it for 3 reasons:
We have to cast the DbContext as IObjectContextAdapter. The method Translate should be on DbContext<T> class IMO.
We have to use classic ADO.NET Objects. Why? Stored Procedures are a must have for any ORM. My main gripe with EF is the lack of the stored procedure support and this seems to not have been rectified with EF CTP5.
You have to open a new SqlConnection. Why can't it use the same connection as the one opened by the EF Context?
Hope this both helps someone and sends out a message to the EF team. We need multiple result support for SPROCS off the shelf. You can map a stored proc to a complex type, so why can't we map a stored proc to multiple complex types?

L2S DataContext out of synch: row not found or changed

Psssst...!
Read on, by all means. But I can tell you here that the problem had nothing to do with the DataContext, but with Dependency Injection. I have left the question up, as it documents one possible issue with the "row not found or changed error" that has nothing to do with real world concurrency conflicts.
It seems the problems have been caused by badly written dependency injection. Or rather, I am beginning to believe, by default lifecycle management by the DI container I used.
The problem was that I used a DataContext as a constructor argument that was supplied by Ninject. It seems that the default behaviour was to cache this DataContext, leading to all manner of unexpected behaviour. I will ask a separate question about this.
Anyway, what follows is my original question, which as you will see, misses the real cause of the issue by a mile...
The Problem
I am getting a number of errors that imply that the DataContext, or rather, the way I am using the DataContext is getting out of synch.
The error occurs on db.SubmitChanges() where db is my DataContext instance. The error is:
Row not found or changed.
The problem only occurs intermitently, for example, adding a row then deleting it. If I stop the dev server and restart, the added row is there and I can delete it no problem.
Ie, it seems that the problem is related to the DataContext losing track of the rows that have been added.
IMPORTANT:
Before anyone votes to close this thread, on the basis of it being a duplicate, I have checked the sql server profiler and there is no "Where 0 = 1" in the SQL.
I have also recreated the dbml file, so am satisfied that the database schema is in synch with the schema represented by the dbml file.
Ie, no cases of mismatched nullable/not nullable columns, etc.
My Diagnosis (for what it is worth):
The problem seems (to me) related to how I am using the DataContext. I am new to MVC, Repositories and Services patterns, so suspect that I have wired things up wrong.
The Setup
Simple eLearning app in its early stages. Pupils need to be able to add and delete courses (Courses table) to their UserCourses.
To do this, I have a service that gets a specific DataContext instance Dependency Injected into its constructor.
Service Class Constructor:
public class SqlPupilBlockService : IPupilBlockService
{
DataContext db;
public SqlPupilBlockService(DataContext db)
{
this.db = db;
CoursesRepository = new SqlRepository<Course>(db);
UserCoursesRepository = new SqlRepository<UserCourse>(db);
}
// Etc, etc
}
The CoursesRepository and UserCoursesRepository are both private properties of the service class that are of type IRepository (just a simple generic repository interface).
SqlRepository Code:
public class SqlRepository<T> : IRepository<T> where T : class
{
DataContext db;
public SqlRepository(DataContext db)
{
this.db = db;
}
#region IRepository<T> Members
public IQueryable<T> Query
{
get { return db.GetTable<T>(); }
}
public List<T> FetchAll()
{
return Query.ToList();
}
public void Add(T entity)
{
db.GetTable<T>().InsertOnSubmit(entity);
}
public void Delete(T entity)
{
db.GetTable<T>().DeleteOnSubmit(entity);
}
public void Save()
{
db.SubmitChanges();
}
#endregion
}
The two methods for adding and deleting UserCourses are:
Service Methods for Adding and Deleting UserCourses:
public void AddUserCourse(int courseId)
{
UserCourse uc = new UserCourse();
uc.IdCourse = courseId;
uc.IdUser = UserId;
uc.DateCreated = DateTime.Now;
uc.DateAmended = DateTime.Now;
uc.Role = "Pupil";
uc.CourseNotes = string.Empty;
uc.ActiveStepIndex = 0;
UserCoursesRepository.Add(uc);
UserCoursesRepository.Save();
}
public void DeleteUserCourse(int courseId)
{
var uc = (UserCoursesRepository.Query.Where(x => x.IdUser == UserId && x.IdCourse == courseId)).Single();
UserCoursesRepository.Delete(uc);
UserCoursesRepository.Save();
}
Ajax
I am using Ajax via Ajax.BeginForm
I don't think that is relevant.
ASP.NET MVC 3
I am using mvc3, but don't think that is relevant: the errors are related to model code.
The problem only occurs intermitently,
for example, adding a row then
deleting it. If I stop the dev server
and restart, the added row is there
and I can delete it no problem.
Your code does not show what the link is between the Added Row and the Delete/Update. Your Add() doesn't return an object reference.
I'm thinking you are missing a Refresh (ie reload the object after Insert). Is your IdCourse also the PK in the Table?
Edit:
Further research has revealed that the problem is with the dependency injection.
The problem was related to how Dependency Injection manages the items it creates. Google for 'lifecycle management' in IoC or DI. Essentially, DI cached a DataContext constructor argument that I injected.
For a way to solve this using the Factory Pattern, see this thread:
Ninject caching an injected DataContext? Lifecycle Management?
The accepted answer solved it all.

The specified LINQ expression contains references to queries that are associated with different contexts

am getting this error on this code (this is an MVC project into which I am trying to integrate Entity Framework):
List<string> consultantSchoolList = new List<string>();
// districts managed by consultant
IQueryable<string> consultClients = rc.consultantDistrictsRepository.districtsForConsultant(userID);
// schools managed by consultant
if (consultClients != null)
{
consultantSchoolList = (from c in rc.clientsRepository.Clients
where (consultClients.Contains(c.cdsCode.Substring(0, 7)))
select c.cdsCode).ToList();
}
on the "consultantSchoolList = " line.
This is an MVC project and I am using a context object that is stored in the HttpContext.Current object. Each repository has a private variable that stores the context object, but each one should point to the same object in the HttpContext.Current Items collection. Would this be considered two different contexts even though they point to the same thing?
Stepping through the code in the debugger shows that the context objects for the two repositories, consultantDistrictsRepository and clientsRepository, do point to the same object in the HttpContext.Current object.
UPDATE Here's how I define the context objects in each repository:
private SchedulerContext context;
public EFConsultantDistricts()
{
context = ContextHelper.GetContext();
}
and GetContext is as follows:
public static SchedulerContext GetContext()
{
if (!HttpContext.Current.Items.Contains("_db_context"))
{
HttpContext.Current.Items.Add("_db_context", new SchedulerContext());
}
return (SchedulerContext)HttpContext.Current.Items["_db_context"];
}
I found the problem -- I was caching the frequently-requested clients list in a Session variable in the Clients repository:
if (HttpContext.Current.Session["clientList"] == null)
{
HttpContext.Current.Session["clientList"] = from c in context.Clients
where (c.Year == fiscalYear)
select c;
}
return (IQueryable<Client>)HttpContext.Current.Session["clientList"];
Since the Session object persists over requests, I guess it was using a previous context object. I thought the client list would be separated from the context object, but I guess not if I'm using IQueryable.
I hate to hit the database every time for this, but I guess I have no choice ... at least for the moment until I get everything straightened out.

Resources