I want to use MiniProfiler to call a function once a set time limit has gone by. This is how MiniProfiler is set up. After that I've included the profiling script which we use to profile whatever needs profiling. My problem is to create some sort of middleware that can intercept this "MiniProfiler.Current.Step" call when the timing is greater than 1000ms.
app.UseMiniProfiler(new MiniProfilerOptions
{
ResultsAuthorize = x => false,
ResultsListAuthorize = x => false,
Storage = new SqlServerStorage(Configuration.GetConnectionString("MiniProfiler"))
});
MiniProfilerEF6.Initialize();
app.Use(async (context, next) =>
{
MiniProfiler.Current.Name = context.Request.GetDisplayUrl();
await next.Invoke();
});
/// <summary>
/// The main profiler, useage:
/// using(this.Profile("more context"))
/// {
/// Do things that needs profiling, and you may nest it.
/// }
/// </summary>
/// <param name="profiled">The object that is profiled</param>
/// <param name="subSectionName">More context for the output result</param>
/// <param name="methodName">Possible override for the method name called</param>
/// <param name="profiledTypeName">Possible override for the profiled type</param>
/// <returns>An IDisposable, to help with scoping</returns>
public static IDisposable Profile(this object profiled,
string subSectionName = null,
[CallerMemberName] string methodName = "",
string profiledTypeName = null)
{
if (profiled == null)
throw new ArgumentNullException(nameof(profiled));
var profiledType = profiledTypeName ?? profiled.GetType().Name;
return Profile(methodName, profiledType, subSectionName);
}
public static IDisposable Profile(string methodName,
string profiledTypeName,
string subSectionName = null)
{
var name = subSectionName != null
? $"{profiledTypeName}.{methodName}:{subSectionName}"
: $"{profiledTypeName}.{methodName}";
return MiniProfiler.Current?.Step(name);
}
Related
So I'm following an example of setting up LoggerMiddleware which is demonstrated here which I've adapted to just use .NET Core's ILogger. We already had a LoggerModule which work pre v6 so I copied the creation code into the LoggerMiddleware class to suit.
What I don't understand is how to register this. There's no default constructor on the Module so I can't use builder.RegisterModule<MiddlewareModule>(). I know I can pre-create the MiddlewareModule passing in an instance of the LoggerMiddleware then builder.RegisterModule(moduleInstance) but this MiddlewareModule is supposed to be used for all Middleware we create. This doco page doesn't elaborate on that final detail. Do I just do this per instance of IResolveMiddleware?:
var loggerMiddlewareModule = new MiddlewareModule(new LoggerMiddleware());
builder.RegisterModule(loggerMiddlewareModule);
var otherMiddlewareModule = new MiddlewareModule(new OtherMiddleware());
builder.RegisterModule(otherMiddlewareModule);
My Middleware and Module:
public class LoggerMiddleware : IResolveMiddleware
{
public PipelinePhase Phase => PipelinePhase.ParameterSelection;
public void Execute(ResolveRequestContext context, Action<ResolveRequestContext> next)
{
var t = context.Registration.Activator.LimitType;
// Add our parameters.
context.ChangeParameters(context.Parameters.Union(
new[]
{
new ResolvedParameter((p, i) => p.ParameterType == typeof(ILogger), (p, i) => GetLogger(i, t)),
new ResolvedParameter(
(p, i) => p.ParameterType.GenericTypeArguments.Any() &&
p.ParameterType.GetGenericTypeDefinition() == typeof(ILogger<>),
(p, i) => GetGenericTypeLogger(i, t))
}));
// Continue the resolve.
next(context);
// Has an instance been activated?
if (context.NewInstanceActivated)
{
var instanceType = context.Instance.GetType();
// Get all the injectable properties to set.
// If you wanted to ensure the properties were only UNSET properties,
// here's where you'd do it.
var properties = instanceType
.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Where(p => p.PropertyType == typeof(ILogger) && p.CanWrite && p.GetIndexParameters().Length == 0);
// Set the properties located.
foreach (var propToSet in properties)
{
propToSet.SetValue(context.Instance, GetLogger(context, instanceType), null);
}
}
}
/// <summary>
/// The log cache.
/// </summary>
private static readonly ConcurrentDictionary<Type, object> _logCache = new ConcurrentDictionary<Type, object>();
/// <summary>
/// Gets the logger.
/// </summary>
/// <param name="context">The component context.</param>
/// <param name="declaringType">The type of logger.</param>
/// <returns>The logger instance.</returns>
private static object GetGenericTypeLogger(IComponentContext context, Type declaringType)
{
return _logCache.GetOrAdd(
declaringType,
x =>
{
var wrapper = typeof(LoggerWrapper<>);
var specificWrapper = wrapper.MakeGenericType(declaringType);
var instance = (ILoggerWrapper)Activator.CreateInstance(specificWrapper);
var factory = context.Resolve<ILoggerFactory>();
return instance!.Create(factory);
});
}
/// <summary>
/// Gets the logger.
/// </summary>
/// <param name="context">The component context.</param>
/// <param name="declaringType">The type of logger.</param>
/// <returns>The logger instance.</returns>
private static object GetLogger(IComponentContext context, Type declaringType)
{
return _logCache.GetOrAdd(
declaringType,
x =>
{
var factory = context.Resolve<ILoggerFactory>();
return factory.CreateLogger(declaringType);
});
}
/// <summary>
/// The <see cref="ILoggerWrapper" />
/// interface defines the method for creating a generic type logger.
/// </summary>
private interface ILoggerWrapper
{
/// <summary>
/// Creates a generic type logger using the specified factory.
/// </summary>
/// <param name="factory">The factory.</param>
/// <returns>The logger.</returns>
object Create(ILoggerFactory factory);
}
private class LoggerWrapper<T> : ILoggerWrapper
{
public object Create(ILoggerFactory factory)
{
return factory.CreateLogger<T>();
}
}
}
public class MiddlewareModule : Module
{
private readonly IResolveMiddleware _middleware;
public MiddlewareModule(IResolveMiddleware middleware)
{
_middleware = middleware;
}
protected override void AttachToComponentRegistration(IComponentRegistryBuilder componentRegistry, IComponentRegistration registration)
{
// Attach to the registrations pipeline build
registration.PipelineBuilding += (sender, pipeline) =>
{
// Add our middleware to the pipeline
pipeline.Use(_middleware);
};
}
===============
Further question:
So when I run this I'm getting DependencyResolutionException and not quite sure where to look.
This is what the middleware module registration looks like:
There are other standard modules doing RegisterType and Register calls.
You've got it right; that example MiddlewareModule registers a single instance of an IResolveMiddleware in the pipeline of every registration, so you need one instance of the module for each different middleware you want to add.
You could equally have a custom Module that registers multiple middleware, or construct a different middleware instance per-registration; this was just an example.
I am using EF, but I have lazy loading disabled. Instead I am using eager loading, so I created my own service:
/// <summary>
/// Generic service for entity framework
/// </summary>
/// <typeparam name="T">An entity model</typeparam>
public class Service<T> : IService<T> where T : class
{
// Create our private properties
private readonly DbContext _context;
private readonly DbSet<T> _dbEntitySet;
/// <summary>
/// Default constructor
/// </summary>
/// <param name="context">The database context</param>
public Service(DbContext context)
{
// Assign our context and entity set
_context = context ?? throw new ArgumentNullException("context");
_dbEntitySet = context.Set<T>();
}
/// <summary>
/// Gets all the entities
/// </summary>
/// <param name="includes">Option includes for eager loading</param>
/// <returns></returns>
public IQueryable<T> List(params string[] includes)
{
// Create a query
IQueryable<T> query = _dbEntitySet;
// For each include, append to our query
if (includes != null)
foreach (var include in includes)
query = query.Include(include);
// Return our query
return query;
}
/// <summary>
/// Creates an entity
/// </summary>
/// <param name="model"></param>
public void Create(T model) => _dbEntitySet.Add(model);
/// <summary>
/// Updates an entity
/// </summary>
/// <param name="model"></param>
public void Update(T model) => _context.Entry<T>(model).State = EntityState.Modified;
/// <summary>
/// Removes an entity
/// </summary>
/// <param name="model"></param>
public void Remove(T model) => _context.Entry<T>(model).State = EntityState.Deleted;
/// <summary>
/// Saves the database context changes
/// </summary>
/// <returns></returns>
public async Task SaveChangesAsync()
{
try
{
// Save the changes to the database
await _context.SaveChangesAsync();
}
catch (DbEntityValidationException ex)
{
// Retrieve the error messages as a list of strings.
var errorMessages = ex.EntityValidationErrors.SelectMany(x => x.ValidationErrors).Select(x => x.ErrorMessage);
// Join the list to a single string.
var fullErrorMessage = string.Join("; ", errorMessages);
// Combine the original exception message with the new one.
var exceptionMessage = string.Concat(ex.Message, " The validation errors are: ", fullErrorMessage);
// Throw a new DbEntityValidationException with the improved exception message.
throw new DbEntityValidationException(exceptionMessage, ex.EntityValidationErrors);
}
catch (DbUpdateException ex)
{
throw;
}
}
/// <summary>
/// Executes a stored procedure in sql
/// </summary>
/// <param name="procedure">The name of the sproc</param>
/// <param name="parameters">the sql params for the sproc</param>
/// <returns></returns>
public DbRawSqlQuery<T> ExecuteProcedure(string procedure, List<SqlParameter> parameters)
{
var results = _context.Database.SqlQuery<T>($"exec {procedure} { CreateQueryStringFromParams(parameters) }");
return results;
}
/// <summary>
/// Dispose
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>
/// Creates the input string to run sprocs in sql with EF by converting the sql params into a nice string
/// </summary>
/// <param name="parameters"></param>
/// <returns></returns>
private static string CreateQueryStringFromParams(IEnumerable<SqlParameter> parameters)
{
var response = "";
var list = parameters as IList<SqlParameter> ?? parameters.ToList();
var length = list.Count;
for (var i = 0; i < length; i++)
{
response += $"{list[i].ParameterName}=\"{list[i].Value}\"";
if (i != length - 1)
response += ", ";
}
return response;
}
/// <summary>
/// Disposes of any attached resources
/// </summary>
/// <param name="disposing">A boolean indicating whether the object is being disposed</param>
protected virtual void Dispose(bool disposing)
{
// If we are disposing, dispose of our context
if (disposing)
_context.Dispose();
}
}
Each service then inherits this class:
/// <summary>
/// Handles all Group related methods
/// </summary>
public class GroupService : Service<Group>, IGroupService
{
/// <summary>
/// The default constructor
/// </summary>
/// <param name="unitOfWork"></param>
public GroupService(DbContext context) : base(context)
{
}
/// <summary>
/// Lists groups by category
/// </summary>
/// <param name="categoryId">The id of the category</param>
/// <param name="includes"></param>
/// <returns></returns>
public IQueryable<Group> List(int categoryId, params string[] includes) => List(includes).Where(m => m.CategoryId == categoryId);
/// <summary>
/// Gets a single Group by id
/// </summary>
/// <param name="id">The id of the Group</param>
/// <returns></returns>
public async Task<Group> GetAsync(int id, params string[] includes) => await List(includes).Where(model => model.Id == id).SingleOrDefaultAsync();
}
Each "entity" has a class similar to this GroupService.
I also have providers for each entity type too and here is my delete method:
/// <summary>
/// Delete a Group
/// </summary>
/// <param name="id">The Group id</param>
/// <returns></returns>
public async Task<bool> DeleteAsync(int id)
{
// Get our model
var model = await _service.GetAsync(id, "Questions");
// For each question, remove from the database
if (model.Questions != null)
foreach (var question in model.Questions.ToList())
if (!await _questionProvider.Value.DeleteAsync(question.Id, false))
throw new Exception("Failed to delete the questions");
// Update our Questions
model.Questions = null;
// Save our model
_service.Remove(model);
// Save the database changes
await _service.SaveChangesAsync();
// Return true
return true;
}
As you can see, I just pull back the questions.
If there are some questions, then I invoke the questionProvider's delete method, which is very similar:
/// <summary>
/// Delete a question
/// </summary>
/// <param name="id">The question id</param>
/// <param name="saveChanges">Saves the changes to the database after the delete (default true)</param>
/// <returns></returns>
public async Task<bool> DeleteAsync(int id, bool saveChanges = true)
{
// Get our model
var model = await _service.GetAsync(id, "Answers");
// For each answer, delete from the database
if (model.Answers != null)
foreach (var answer in model.Answers.ToList())
if (!await _answerProvider.Value.DeleteAsync(answer.Id, false))
throw new Exception("Failed to delete the answers");
// Update our Answers
model.Answers = null;
// Save our model
_service.Remove(model);
// Save the database changes
if (saveChanges) await _service.SaveChangesAsync();
// Return true
return true;
}
As you can see, I do not save the context changes until all children have been removed. Now I must point out that I am not removing the child directly from the parent. Instead I am removing the entity from it's own collection and then setting the property to null. After it is all done I save the changes, but I am getting this error:
The operation failed: The relationship could not be changed because one or more of the foreign-key properties is non-nullable. When a change is made to a relationship, the related foreign-key property is set to a null value. If the foreign-key does not support null values, a new relationship must be defined, the foreign-key property must be assigned another non-null value, or the unrelated object must be deleted.
Does anyone know how I can delete the entities in a method similar to what I am trying to achieve?
This was rather easy to fix after reading some other people with similar issues. I just changed one line of code in my Service.cs from:
public void Remove(T model) => _context.Entry<T>(model).State = EntityState.Deleted;
to:
public void Remove(T model) => _dbEntitySet.Remove(model);
And that worked.
Update
I also found that the eager load needs to include all the navigation properties that will be affected.
So if I was to delete a Question which has Answers and Answers have Formulas, the Get invocation would have to be:
var model = await _service.GetAsync(id, "Answers.Formulas");
If you don't include that, you will get an error.
I have been going rounds all morning with CF EF. I think I have all issues resolved but one and I'm sure this issue is due to my lack of knowledge / understanding.
The problem is focused around one table and the mapping of said table so I'll give the definitions first then explain the problems. Here is how the table is defined:
Now I have defined the class representation as:
public partial class ExternalForumCredentials : BaseEntity
{
public virtual int Customer_Id { get; set; }
public virtual int ExternalForumBoardId { get; set; }
public virtual string Username { get; set; }
public virtual string Password { get; set; }
}
and the mapping:
public ExternalForumCredentialsMap()
{
this.ToTable("ExternalForumCredentials");
this.HasKey(ef => ef.Id);
this.HasKey(ef => ef.Customer_Id);
this.HasKey(ef => ef.ExternalForumBoardId);
this.Property(ef => ef.Username).IsRequired().HasMaxLength(100);
this.Property(ef => ef.Password).IsRequired().HasMaxLength(100);
}
Now I am not showing the controller code as I think the problem lies in my ef config. HOWEVER if I am wrong simply tell me what I need to add and I will do so immediately.
So...given that ID, Customer_Id, and ExternalForumBoardId are primary keys I defined the mapping as seen above.
With this configuration all goes well right up to an insert...then it crashes saying I am trying to do an IDENTITY_INSERT. OK that makes sense as I am assigning a value to Customer_Id and ExternalForumBoardId in the controller and since the mapping has them as keys there is a conflict.
So I change the mapping to:
public ExternalForumCredentialsMap()
{
this.ToTable("ExternalForumCredentials");
this.HasKey(ef => ef.Id);
this.Property(ef => ef.Customer_Id);
this.Property(ef => ef.ExternalForumBoardId);
this.Property(ef => ef.Username).IsRequired().HasMaxLength(100);
this.Property(ef => ef.Password).IsRequired().HasMaxLength(100);
}
Now before I can even perform the insert I get an error: Invalid column name Customer_Id and of course with my limited knowledge I don't understand because I have define Customer_Id.
I have also tried HasRequired in the mapping but that won't even compile saying "the type int must be a reference type in order to use it as parameter..."
The other mapping options such as Ignore don't seem to make sense in this context.
Any help explaining what I am doing wrong would be much appreciated.
Db Context Interface:
public interface IDbContext
{
IDbSet<TEntity> Set<TEntity>() where TEntity : BaseEntity;
int SaveChanges();
IList<TEntity> ExecuteStoredProcedureList<TEntity>(string commandText, params object[] parameters)
where TEntity : BaseEntity, new();
/// <summary>
/// Creates a raw SQL query that will return elements of the given generic type. The type can be any type that has properties that match the names of the columns returned from the query, or can be a simple primitive type. The type does not have to be an entity type. The results of this query are never tracked by the context even if the type of object returned is an entity type.
/// </summary>
/// <typeparam name="TElement">The type of object returned by the query.</typeparam>
/// <param name="sql">The SQL query string.</param>
/// <param name="parameters">The parameters to apply to the SQL query string.</param>
/// <returns>Result</returns>
IEnumerable<TElement> SqlQuery<TElement>(string sql, params object[] parameters);
/// <summary>
/// Executes the given DDL/DML command against the database.
/// </summary>
/// <param name="sql">The command string</param>
/// <param name="timeout">Timeout value, in seconds. A null value indicates that the default value of the underlying provider will be used</param>
/// <param name="parameters">The parameters to apply to the command string.</param>
/// <returns>The result returned by the database after executing the command.</returns>
int ExecuteSqlCommand(string sql, int? timeout = null, params object[] parameters);
}
}
and:
public static class DbContextExtensions {
/// <summary>
/// Loads the database copy.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="context">The context.</param>
/// <param name="currentCopy">The current copy.</param>
/// <returns></returns>
public static T LoadDatabaseCopy<T>(this IDbContext context, T currentCopy) where T : BaseEntity {
return InnerGetCopy(context, currentCopy, e => e.GetDatabaseValues());
}
private static T InnerGetCopy<T>(IDbContext context, T currentCopy, Func<DbEntityEntry<T>, DbPropertyValues> func) where T : BaseEntity {
//Get the database context
DbContext dbContext = CastOrThrow(context);
//Get the entity tracking object
DbEntityEntry<T> entry = GetEntityOrReturnNull(currentCopy, dbContext);
//The output
T output = null;
//Try and get the values
if (entry != null) {
DbPropertyValues dbPropertyValues = func(entry);
if(dbPropertyValues != null) {
output = dbPropertyValues.ToObject() as T;
}
}
return output;
}
/// <summary>
/// Gets the entity or return null.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="currentCopy">The current copy.</param>
/// <param name="dbContext">The db context.</param>
/// <returns></returns>
private static DbEntityEntry<T> GetEntityOrReturnNull<T>(T currentCopy, DbContext dbContext) where T : BaseEntity {
return dbContext.ChangeTracker.Entries<T>().Where(e => e.Entity == currentCopy).FirstOrDefault();
}
private static DbContext CastOrThrow(IDbContext context) {
DbContext output = (context as DbContext);
if(output == null) {
throw new InvalidOperationException("Context does not support operation.");
}
return output;
}
/// <summary>
/// Loads the original copy.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="context">The context.</param>
/// <param name="currentCopy">The current copy.</param>
/// <returns></returns>
public static T LoadOriginalCopy<T>(this IDbContext context, T currentCopy) where T : BaseEntity {
return InnerGetCopy(context, currentCopy, e => e.OriginalValues);
}
}
and the implementation:
public class ObjectContext : DbContext, IDbContext
{
public ObjectContext(string nameOrConnectionString)
: base(nameOrConnectionString)
{
//((IObjectContextAdapter) this).ObjectContext.ContextOptions.LazyLoadingEnabled = true;
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
//dynamically load all configuration
System.Type configType = typeof(LanguageMap); //any of your configuration classes here
var typesToRegister = Assembly.GetAssembly(configType).GetTypes()
.Where(type => !String.IsNullOrEmpty(type.Namespace))
.Where(type => type.BaseType != null && type.BaseType.IsGenericType && type.BaseType.GetGenericTypeDefinition() == typeof(EntityTypeConfiguration<>));
foreach (var type in typesToRegister)
{
dynamic configurationInstance = Activator.CreateInstance(type);
modelBuilder.Configurations.Add(configurationInstance);
}
//...or do it manually below. For example,
//modelBuilder.Configurations.Add(new LanguageMap());
base.OnModelCreating(modelBuilder);
}
/// <summary>
/// Attach an entity to the context or return an already attached entity (if it was already attached)
/// </summary>
/// <typeparam name="TEntity">TEntity</typeparam>
/// <param name="entity">Entity</param>
/// <returns>Attached entity</returns>
protected virtual TEntity AttachEntityToContext<TEntity>(TEntity entity) where TEntity : BaseEntity, new()
{
//little hack here until Entity Framework really supports stored procedures
//otherwise, navigation properties of loaded entities are not loaded until an entity is attached to the context
var alreadyAttached = Set<TEntity>().Local.Where(x => x.Id == entity.Id).FirstOrDefault();
if (alreadyAttached == null)
{
//attach new entity
Set<TEntity>().Attach(entity);
return entity;
}
else
{
//entity is already loaded.
return alreadyAttached;
}
}
public string CreateDatabaseScript()
{
return ((IObjectContextAdapter)this).ObjectContext.CreateDatabaseScript();
}
public new IDbSet<TEntity> Set<TEntity>() where TEntity : BaseEntity
{
return base.Set<TEntity>();
}
public IList<TEntity> ExecuteStoredProcedureList<TEntity>(string commandText, params object[] parameters) where TEntity : BaseEntity, new()
{
//HACK: Entity Framework Code First doesn't support doesn't support output parameters
//That's why we have to manually create command and execute it.
//just wait until EF Code First starts support them
//
//More info: http://weblogs.asp.net/dwahlin/archive/2011/09/23/using-entity-framework-code-first-with-stored-procedures-that-have-output-parameters.aspx
bool hasOutputParameters = false;
if (parameters != null)
{
foreach (var p in parameters)
{
var outputP = p as DbParameter;
if (outputP == null)
continue;
if (outputP.Direction == ParameterDirection.InputOutput ||
outputP.Direction == ParameterDirection.Output)
hasOutputParameters = true;
}
}
var context = ((IObjectContextAdapter)(this)).ObjectContext;
if (!hasOutputParameters)
{
//no output parameters
var result = this.Database.SqlQuery<TEntity>(commandText, parameters).ToList();
for (int i = 0; i < result.Count; i++)
result[i] = AttachEntityToContext(result[i]);
return result;
//var result = context.ExecuteStoreQuery<TEntity>(commandText, parameters).ToList();
//foreach (var entity in result)
// Set<TEntity>().Attach(entity);
//return result;
}
else
{
//var connection = context.Connection;
var connection = this.Database.Connection;
//Don't close the connection after command execution
//open the connection for use
if (connection.State == ConnectionState.Closed)
connection.Open();
//create a command object
using (var cmd = connection.CreateCommand())
{
//command to execute
cmd.CommandText = commandText;
cmd.CommandType = CommandType.StoredProcedure;
// move parameters to command object
if (parameters != null)
foreach (var p in parameters)
cmd.Parameters.Add(p);
//database call
var reader = cmd.ExecuteReader();
//return reader.DataReaderToObjectList<TEntity>();
var result = context.Translate<TEntity>(reader).ToList();
for (int i = 0; i < result.Count; i++)
result[i] = AttachEntityToContext(result[i]);
//close up the reader, we're done saving results
reader.Close();
return result;
}
}
}
/// <summary>
/// Creates a raw SQL query that will return elements of the given generic type. The type can be any type that has properties that match the names of the columns returned from the query, or can be a simple primitive type. The type does not have to be an entity type. The results of this query are never tracked by the context even if the type of object returned is an entity type.
/// </summary>
/// <typeparam name="TElement">The type of object returned by the query.</typeparam>
/// <param name="sql">The SQL query string.</param>
/// <param name="parameters">The parameters to apply to the SQL query string.</param>
/// <returns>Result</returns>
public IEnumerable<TElement> SqlQuery<TElement>(string sql, params object[] parameters)
{
return this.Database.SqlQuery<TElement>(sql, parameters);
}
/// <summary>
/// Executes the given DDL/DML command against the database.
/// </summary>
/// <param name="sql">The command string</param>
/// <param name="timeout">Timeout value, in seconds. A null value indicates that the default value of the underlying provider will be used</param>
/// <param name="parameters">The parameters to apply to the command string.</param>
/// <returns>The result returned by the database after executing the command.</returns>
public int ExecuteSqlCommand(string sql, int? timeout = null, params object[] parameters)
{
int? previousTimeout = null;
if (timeout.HasValue)
{
//store previous timeout
previousTimeout = ((IObjectContextAdapter) this).ObjectContext.CommandTimeout;
((IObjectContextAdapter) this).ObjectContext.CommandTimeout = timeout;
}
var result = this.Database.ExecuteSqlCommand(sql, parameters);
if (timeout.HasValue)
{
//Set previous timeout back
((IObjectContextAdapter) this).ObjectContext.CommandTimeout = previousTimeout;
}
//return result
return result;
}
}
You have another class that has a relationship with ExternalForumCredentials and it hasn't be configured properly. EntityFramework will try its best to guess your conventions, so it's guessing that there is a Customer_Id that it can connect to.
Go to any maps that are using ExternalForumCredentials and configure them properly. For example:
this.HasMany(ef => ef.ExternalForumCredentials)
.WithRequired()
.HasForeignKey(ef => ef.Customer_Id);
I'm trying to apply some behavior using a home grown type of "aspect", really a .net Attribute. I have a base class (BankingServiceBase) that reflects on itself at startup to see what "aspects" are applied to it. It then can execute custom behavior before or after operations. I'm using Autofac as my IOC container. I'm trying to apply the PropertiesAutowired method to the aspect's registration. In the below sample code I want Autofac to inject an ILog instance to my aspect/attribute. It isn't doing that however. My guess is that when I call GetCustomAttributes, it's creating a new instance instead of getting the registered instance from Autofac. Thoughts? Here is some usable sample code to display the problem:
internal class Program
{
private static void Main()
{
var builder = new ContainerBuilder();
builder
.RegisterType<ConsoleLog>()
.As<ILog>();
builder
.RegisterType<BankingService>()
.As<IBankingService>();
builder
.RegisterType<LogTransfer>()
.As<LogTransfer>()
.PropertiesAutowired();
var container = builder.Build();
var bankingService = container.Resolve<IBankingService>();
bankingService.Transfer("ACT 1", "ACT 2", 180);
System.Console.ReadKey();
}
public interface IBankingService
{
void Transfer(string from, string to, decimal amount);
}
public interface ILog
{
void LogMessage(string message);
}
public class ConsoleLog : ILog
{
public void LogMessage(string message)
{
System.Console.WriteLine(message);
}
}
[AttributeUsage(AttributeTargets.Class)]
public abstract class BankingServiceAspect : Attribute
{
public virtual void PreTransfer(string from, string to, decimal amount)
{
}
public virtual void PostTransfer(bool success)
{
}
}
public class LogTransfer : BankingServiceAspect
{
// Note: this is never getting set from Autofac!
public ILog Log { get; set; }
public override void PreTransfer(string from, string to, decimal amount)
{
Log.LogMessage(string.Format("About to transfer from {0}, to {1}, for amount {2}", from, to, amount));
}
public override void PostTransfer(bool success)
{
Log.LogMessage(success ? "Transfer completed!" : "Transfer failed!");
}
}
public abstract class BankingServiceBase : IBankingService
{
private readonly List<BankingServiceAspect> aspects;
protected BankingServiceBase()
{
// Note: My guess is that this "GetCustomAttributes" is happening before the IOC dependency map is built.
aspects =
GetType().GetCustomAttributes(typeof (BankingServiceAspect), true).Cast<BankingServiceAspect>().
ToList();
}
void IBankingService.Transfer(string from, string to, decimal amount)
{
aspects.ForEach(a => a.PreTransfer(from, to, amount));
try
{
Transfer(from, to, amount);
aspects.ForEach(a => a.PostTransfer(true));
}
catch (Exception)
{
aspects.ForEach(a => a.PostTransfer(false));
}
}
public abstract void Transfer(string from, string to, decimal amount);
}
[LogTransfer]
public class BankingService : BankingServiceBase
{
public override void Transfer(string from, string to, decimal amount)
{
// Simulate some latency..
Thread.Sleep(1000);
}
}
}
You're correct that GetCustomAttributes doesn't resolve the custom attributes via Autofac - if you think about it, how could FCL code such as GetCustomAttributes know about Autofac? The custom attributes are actually retrieved from assembly metadata, so they never go through Autofac's resolution process and therefore your registration code is never used.
What you can do is to inject the services into the attribute instance yourself. Begin with the code in Oliver's answer to generate the list of aspect attributes. However, before returning the list, you can process each attribute and inject services into any dependent fields and properties. I have a class called AttributedDependencyInjector, which I use via an extension method. It uses reflection to scan for fields and properties that are decorated with the InjectDependencyAttribute and then set the value of those properties. There's rather a lot of code to cope with various scenarios, but here it is.
The attribute class:
/// <summary>
/// Attribute that signals that a dependency should be injected.
/// </summary>
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false, Inherited = true)]
public sealed class InjectDependencyAttribute : Attribute
{
/// <summary>
/// Initializes a new instance of the <see cref = "InjectDependencyAttribute" /> class.
/// </summary>
public InjectDependencyAttribute()
{
this.PreserveExistingValue = false;
}
/// <summary>
/// Gets or sets a value indicating whether to preserve an existing non-null value.
/// </summary>
/// <value>
/// <c>true</c> if the injector should preserve an existing value; otherwise, <c>false</c>.
/// </value>
public bool PreserveExistingValue { get; set; }
}
The injector class:
public class AttributedDependencyInjector
{
/// <summary>
/// The component context.
/// </summary>
private readonly IComponentContext context;
/// <summary>
/// Initializes a new instance of the <see cref="AttributedDependencyInjector"/> class.
/// </summary>
/// <param name="context">The context.</param>
public AttributedDependencyInjector(IComponentContext context)
{
this.context = context;
}
/// <summary>
/// Injects dependencies into an instance.
/// </summary>
/// <param name="instance">The instance.</param>
public void InjectDependencies(object instance)
{
this.InjectAttributedFields(instance);
this.InjectAttributedProperties(instance);
}
/// <summary>
/// Gets the injectable fields.
/// </summary>
/// <param name="instanceType">
/// Type of the instance.
/// </param>
/// <param name="injectableFields">
/// The injectable fields.
/// </param>
private static void GetInjectableFields(
Type instanceType, ICollection<Tuple<FieldInfo, InjectDependencyAttribute>> injectableFields)
{
const BindingFlags BindingsFlag =
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly;
IEnumerable<FieldInfo> fields = instanceType.GetFields(BindingsFlag);
// fields
foreach (FieldInfo field in fields)
{
Type fieldType = field.FieldType;
if (fieldType.IsValueType)
{
continue;
}
// Check if it has an InjectDependencyAttribute
var attribute = field.GetAttribute<InjectDependencyAttribute>(false);
if (attribute == null)
{
continue;
}
var info = new Tuple<FieldInfo, InjectDependencyAttribute>(field, attribute);
injectableFields.Add(info);
}
}
/// <summary>
/// Gets the injectable properties.
/// </summary>
/// <param name="instanceType">
/// Type of the instance.
/// </param>
/// <param name="injectableProperties">
/// A list into which are appended any injectable properties.
/// </param>
private static void GetInjectableProperties(
Type instanceType, ICollection<Tuple<PropertyInfo, InjectDependencyAttribute>> injectableProperties)
{
// properties
foreach (var property in instanceType.GetProperties(
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly))
{
Type propertyType = property.PropertyType;
// Can't inject value types
if (propertyType.IsValueType)
{
continue;
}
// Can't inject non-writeable properties
if (!property.CanWrite)
{
continue;
}
// Check if it has an InjectDependencyAttribute
var attribute = property.GetAttribute<InjectDependencyAttribute>(false);
if (attribute == null)
{
continue;
}
// If set to preserve existing value, we must be able to read it!
if (attribute.PreserveExistingValue && !property.CanRead)
{
throw new BoneheadedException("Can't preserve an existing value if it is unreadable");
}
var info = new Tuple<PropertyInfo, InjectDependencyAttribute>(property, attribute);
injectableProperties.Add(info);
}
}
/// <summary>
/// Determines whether the <paramref name="propertyType"/> can be resolved in the specified context.
/// </summary>
/// <param name="propertyType">
/// Type of the property.
/// </param>
/// <returns>
/// <c>true</c> if <see cref="context"/> can resolve the specified property type; otherwise, <c>false</c>.
/// </returns>
private bool CanResolve(Type propertyType)
{
return this.context.IsRegistered(propertyType) || propertyType.IsAssignableFrom(typeof(ILog));
}
/// <summary>
/// Injects dependencies into the instance's fields.
/// </summary>
/// <param name="instance">
/// The instance.
/// </param>
private void InjectAttributedFields(object instance)
{
Type instanceType = instance.GetType();
// We can't get information about the private members of base classes through reflecting a subclass,
// so we must walk up the inheritance hierarchy and reflect at each level
var injectableFields = new List<Tuple<FieldInfo, InjectDependencyAttribute>>();
var type = instanceType;
while (type != null)
{
GetInjectableFields(type, injectableFields);
type = type.BaseType;
}
// fields
foreach (var fieldDetails in injectableFields)
{
var field = fieldDetails.Item1;
var attribute = fieldDetails.Item2;
if (!this.CanResolve(field.FieldType))
{
continue;
}
// Check to preserve existing value
if (attribute.PreserveExistingValue && (field.GetValue(instance) != null))
{
continue;
}
object fieldValue = this.Resolve(field.FieldType, instanceType);
field.SetValue(instance, fieldValue);
}
}
/// <summary>
/// Injects dependencies into the instance's properties.
/// </summary>
/// <param name="instance">
/// The instance.
/// </param>
private void InjectAttributedProperties(object instance)
{
Type instanceType = instance.GetType();
// We can't get information about the private members of base classes through reflecting a subclass,
// so we must walk up the inheritance bierarchy and reflect at each level
var injectableProperties = new List<Tuple<PropertyInfo, InjectDependencyAttribute>>();
var type = instanceType;
while (type != typeof(object))
{
Debug.Assert(type != null, "type != null");
GetInjectableProperties(type, injectableProperties);
type = type.BaseType;
}
// Process the list and inject properties as appropriate
foreach (var details in injectableProperties)
{
var property = details.Item1;
var attribute = details.Item2;
// Check to preserve existing value
if (attribute.PreserveExistingValue && (property.GetValue(instance, null) != null))
{
continue;
}
var propertyValue = this.Resolve(property.PropertyType, instanceType);
property.SetValue(instance, propertyValue, null);
}
}
/// <summary>
/// Resolves the specified <paramref name="propertyType"/> within the context.
/// </summary>
/// <param name="propertyType">
/// Type of the property that is being injected.
/// </param>
/// <param name="instanceType">
/// Type of the object that is being injected.
/// </param>
/// <returns>
/// The object instance to inject into the property value.
/// </returns>
private object Resolve(Type propertyType, Type instanceType)
{
if (propertyType.IsAssignableFrom(typeof(ILog)))
{
return LogManager.GetLogger(instanceType);
}
return this.context.Resolve(propertyType);
}
}
The extension method:
public static class RegistrationExtensions
{
/// <summary>
/// Injects dependencies into the instance's properties and fields.
/// </summary>
/// <param name="context">
/// The component context.
/// </param>
/// <param name="instance">
/// The instance into which to inject dependencies.
/// </param>
public static void InjectDependencies(this IComponentContext context, object instance)
{
Enforce.ArgumentNotNull(context, "context");
Enforce.ArgumentNotNull(instance, "instance");
var injector = new AttributedDependencyInjector(context);
injector.InjectDependencies(instance);
}
}
Try to implement a lazy loading of the aspects
private readonly List<BankingServiceAspect> _aspects;
private List<BankingServiceAspect> Aspects
{
get
{
if (_aspects == null) {
_aspects = GetType()
.GetCustomAttributes(typeof(BankingServiceAspect), true)
.Cast<BankingServiceAspect>()
.ToList();
}
return _aspects;
}
}
Then use it like this
Aspects.ForEach(a => a.PreTransfer(from, to, amount));
...
I'm building an app in .NET and C#, and I'd like to cache some of the results by using attributes/annotations instead of explicit code in the method.
I'd like a method signature that looks a bit like this:
[Cache, timeToLive=60]
String getName(string id, string location)
It should make a hash based on the inputs, and use that as the key for the result.
Naturally, there'd be some config file telling it how to actually put in memcached, local dictionary or something.
Do you know of such a framework?
I'd even be interested in one for Java as well
With CacheHandler in Microsoft Enterprise Library you can easily achieve this.
For instance:
[CacheHandler(0, 30, 0)]
public Object GetData(Object input)
{
}
would make all calls to that method cached for 30 minutes. All invocations gets a unique cache-key based on the input data and method name so if you call the method twice with different input it doesn't get cached but if you call it >1 times within the timout interval with the same input then the method only gets executed once.
I've added some extra features to Microsoft's code:
My modified version looks like this:
using System;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.Remoting.Contexts;
using System.Text;
using System.Web;
using System.Web.Caching;
using System.Web.UI;
using Microsoft.Practices.EnterpriseLibrary.Common.Configuration;
using Microsoft.Practices.Unity.InterceptionExtension;
namespace Middleware.Cache
{
/// <summary>
/// An <see cref="ICallHandler"/> that implements caching of the return values of
/// methods. This handler stores the return value in the ASP.NET cache or the Items object of the current request.
/// </summary>
[ConfigurationElementType(typeof (CacheHandler)), Synchronization]
public class CacheHandler : ICallHandler
{
/// <summary>
/// The default expiration time for the cached entries: 5 minutes
/// </summary>
public static readonly TimeSpan DefaultExpirationTime = new TimeSpan(0, 5, 0);
private readonly object cachedData;
private readonly DefaultCacheKeyGenerator keyGenerator;
private readonly bool storeOnlyForThisRequest = true;
private TimeSpan expirationTime;
private GetNextHandlerDelegate getNext;
private IMethodInvocation input;
public CacheHandler(TimeSpan expirationTime, bool storeOnlyForThisRequest)
{
keyGenerator = new DefaultCacheKeyGenerator();
this.expirationTime = expirationTime;
this.storeOnlyForThisRequest = storeOnlyForThisRequest;
}
/// <summary>
/// This constructor is used when we wrap cached data in a CacheHandler so that
/// we can reload the object after it has been removed from the cache.
/// </summary>
/// <param name="expirationTime"></param>
/// <param name="storeOnlyForThisRequest"></param>
/// <param name="input"></param>
/// <param name="getNext"></param>
/// <param name="cachedData"></param>
public CacheHandler(TimeSpan expirationTime, bool storeOnlyForThisRequest,
IMethodInvocation input, GetNextHandlerDelegate getNext,
object cachedData)
: this(expirationTime, storeOnlyForThisRequest)
{
this.input = input;
this.getNext = getNext;
this.cachedData = cachedData;
}
/// <summary>
/// Gets or sets the expiration time for cache data.
/// </summary>
/// <value>The expiration time.</value>
public TimeSpan ExpirationTime
{
get { return expirationTime; }
set { expirationTime = value; }
}
#region ICallHandler Members
/// <summary>
/// Implements the caching behavior of this handler.
/// </summary>
/// <param name="input"><see cref="IMethodInvocation"/> object describing the current call.</param>
/// <param name="getNext">delegate used to get the next handler in the current pipeline.</param>
/// <returns>Return value from target method, or cached result if previous inputs have been seen.</returns>
public IMethodReturn Invoke(IMethodInvocation input, GetNextHandlerDelegate getNext)
{
lock (input.MethodBase)
{
this.input = input;
this.getNext = getNext;
return loadUsingCache();
}
}
public int Order
{
get { return 0; }
set { }
}
#endregion
private IMethodReturn loadUsingCache()
{
//We need to synchronize calls to the CacheHandler on method level
//to prevent duplicate calls to methods that could be cached.
lock (input.MethodBase)
{
if (TargetMethodReturnsVoid(input) || HttpContext.Current == null)
{
return getNext()(input, getNext);
}
var inputs = new object[input.Inputs.Count];
for (int i = 0; i < inputs.Length; ++i)
{
inputs[i] = input.Inputs[i];
}
string cacheKey = keyGenerator.CreateCacheKey(input.MethodBase, inputs);
object cachedResult = getCachedResult(cacheKey);
if (cachedResult == null)
{
var stopWatch = Stopwatch.StartNew();
var realReturn = getNext()(input, getNext);
stopWatch.Stop();
if (realReturn.Exception == null && realReturn.ReturnValue != null)
{
AddToCache(cacheKey, realReturn.ReturnValue);
}
return realReturn;
}
var cachedReturn = input.CreateMethodReturn(cachedResult, input.Arguments);
return cachedReturn;
}
}
private object getCachedResult(string cacheKey)
{
//When the method uses input that is not serializable
//we cannot create a cache key and can therefore not
//cache the data.
if (cacheKey == null)
{
return null;
}
object cachedValue = !storeOnlyForThisRequest ? HttpRuntime.Cache.Get(cacheKey) : HttpContext.Current.Items[cacheKey];
var cachedValueCast = cachedValue as CacheHandler;
if (cachedValueCast != null)
{
//This is an object that is reloaded when it is being removed.
//It is therefore wrapped in a CacheHandler-object and we must
//unwrap it before returning it.
return cachedValueCast.cachedData;
}
return cachedValue;
}
private static bool TargetMethodReturnsVoid(IMethodInvocation input)
{
var targetMethod = input.MethodBase as MethodInfo;
return targetMethod != null && targetMethod.ReturnType == typeof (void);
}
private void AddToCache(string key, object valueToCache)
{
if (key == null)
{
//When the method uses input that is not serializable
//we cannot create a cache key and can therefore not
//cache the data.
return;
}
if (!storeOnlyForThisRequest)
{
HttpRuntime.Cache.Insert(
key,
valueToCache,
null,
System.Web.Caching.Cache.NoAbsoluteExpiration,
expirationTime,
CacheItemPriority.Normal, null);
}
else
{
HttpContext.Current.Items[key] = valueToCache;
}
}
}
/// <summary>
/// This interface describes classes that can be used to generate cache key strings
/// for the <see cref="CacheHandler"/>.
/// </summary>
public interface ICacheKeyGenerator
{
/// <summary>
/// Creates a cache key for the given method and set of input arguments.
/// </summary>
/// <param name="method">Method being called.</param>
/// <param name="inputs">Input arguments.</param>
/// <returns>A (hopefully) unique string to be used as a cache key.</returns>
string CreateCacheKey(MethodBase method, object[] inputs);
}
/// <summary>
/// The default <see cref="ICacheKeyGenerator"/> used by the <see cref="CacheHandler"/>.
/// </summary>
public class DefaultCacheKeyGenerator : ICacheKeyGenerator
{
private readonly LosFormatter serializer = new LosFormatter(false, "");
#region ICacheKeyGenerator Members
/// <summary>
/// Create a cache key for the given method and set of input arguments.
/// </summary>
/// <param name="method">Method being called.</param>
/// <param name="inputs">Input arguments.</param>
/// <returns>A (hopefully) unique string to be used as a cache key.</returns>
public string CreateCacheKey(MethodBase method, params object[] inputs)
{
try
{
var sb = new StringBuilder();
if (method.DeclaringType != null)
{
sb.Append(method.DeclaringType.FullName);
}
sb.Append(':');
sb.Append(method.Name);
TextWriter writer = new StringWriter(sb);
if (inputs != null)
{
foreach (var input in inputs)
{
sb.Append(':');
if (input != null)
{
//Diffrerent instances of DateTime which represents the same value
//sometimes serialize differently due to some internal variables which are different.
//We therefore serialize it using Ticks instead. instead.
var inputDateTime = input as DateTime?;
if (inputDateTime.HasValue)
{
sb.Append(inputDateTime.Value.Ticks);
}
else
{
//Serialize the input and write it to the key StringBuilder.
serializer.Serialize(writer, input);
}
}
}
}
return sb.ToString();
}
catch
{
//Something went wrong when generating the key (probably an input-value was not serializble.
//Return a null key.
return null;
}
}
#endregion
}
}
Microsoft deserves most credit for this code. We've only added stuff like caching at request level instead of across requests (more useful than you might think) and fixed some bugs (e.g. equal DateTime-objects serializing to different values).
To do exactly what you are describing, i.e. writing
public class MyClass {
[Cache, timeToLive=60]
string getName(string id, string location){
return ExpensiveCall(id, location);
}
}
// ...
MyClass c = new MyClass();
string name = c.getName("id", "location");
string name_again = c.getName("id", "location");
and having only one invocation of the expensive call and without needing to wrap the class with some other code (f.x. CacheHandler<MyClass> c = new CacheHandler<MyClass>(new MyClass());) you need to look into an Aspect Oriented Programming framework. Those usually work by rewriting the byte-code, so you need to add another step to your compilation process - but you gain a lot of power in the process. There are many AOP-frameworks, but PostSharp for .NET and AspectJ are among the most popular. You can easily Google how to use those to add the caching-aspect you want.