I have a controller like below having circular reference in class B.
This is happening because Swashbuckle's jsonserilalizer's setting is set to
ReferenceLoopHandling = ReferenceLoopHandling.Error and I didn't find any way to override this setting.
I am using Swashbuckle 5.6.0 in an ASP.NET MVC application.
public class IssueController : ApiController
{
[HttpGet]
[Route("A")]
public A Get(A input)
{
return new A();
}
}
public class A
{
public virtual B prop1 { get; set; }
}
public class B
{
public virtual B Parent { get; set; }
}
In my case it turned out, that the Stackoverflow exception wasn't caused by the jsonserializer, but by the previous step (when the Swashbuckle schema gets created before json gets serialized). Circle References doesn't seem to work yet with Swashbuckle (in Swashbuckle for it seems to be fixed btw). To fix this, you have to copy the HandleFromUriParamsRecurseSave and add this (with the other filters) to the operations:
private static SwaggerDocument BuildSwaggerDocument()
{
...
var operationFilters = new List<IOperationFilter> {
new HandleFromUriParamsRecurseSave(20),
...
};
}
In the copied HandleFromUriParamsRecurseSave just add a maxrecurselength property, which fits your case and you shouldn't have the StackOverflow error any more:
private void ExtractAndAddQueryParams(
Schema sourceSchema,
string sourceQualifier,
bool? sourceRequired,
SchemaRegistry schemaRegistry,
ICollection<Parameter> operationParams)
{
foreach (var property in sourceSchema.properties)
{
...
var recurseCount = sourceQualifier.Count(t => t == '.');
if (schema.#ref != null && recurseCount < _maxRecurseLength)
{
ExtractAndAddQueryParams(schemaRegistry.Definitions[schema.#ref.Replace("#/definitions/", "")], sourceQualifier + ToCamelCase(property.Key) + ".", flag, schemaRegistry, operationParams);
}
else
{
...
}
}
}
}
A further workaround I tried, but unfortunately didn't solve the issue, was to add a stack and everytime I detected a loop, added the right schema definition just one time (maybe someone sees the problem):
private void ExtractAndAddQueryParams(
Stack<string> sourceSchemaNames,
Schema sourceSchema,
string sourceQualifier,
bool? sourceRequired,
SchemaRegistry schemaRegistry,
ICollection<Parameter> operationParams)
{
if (sourceSchemaNames.Count > _maxRecurseLength) {
return;
}
foreach (var property in sourceSchema.properties)
{
var schema = property.Value;
var readOnly = schema.readOnly;
if (readOnly != true)
{
var flag = sourceRequired == true && sourceSchema.required != null && sourceSchema.required.Contains(property.Key);
var recursionDetected = _disableRecursion && sourceSchemaNames.Contains(schema.#ref);
if (schema.#ref != null && !recursionDetected)
{
sourceSchemaNames.Push(schema.#ref);
ExtractAndAddQueryParams(sourceSchemaNames, schemaRegistry.Definitions[schema.#ref.Replace("#/definitions/", "")],
sourceQualifier + ToCamelCase(property.Key) + ".", flag, schemaRegistry,
operationParams);
sourceSchemaNames.Pop();
}
else
{
...
if (recursionDetected) {
partialSchema.type = "object";
partialSchema.#ref = schema.#ref;
}
operationParams.Add(partialSchema);
}
}
}
}
}
Related
Well, I would like to do my own benchmarking system like spark in Minecraft (https://github.com/lucko/spark):
I'm using Harmony lib (https://github.com/pardeike/Harmony) which allows me to interact/modify methods and allows me to add a Prefix/Postfix on each call that will help me out with this stack.
The basic structure has something similar to (https://github.com/pardeike/Harmony/issues/355):
[HarmonyPatch]
class MyPatches
{
static IEnumerable<MethodBase> TargetMethods()
{
return AccessTools.GetTypesFromAssembly(Assembly.GetExecutingAssembly())
.SelectMany(type => type.GetMethods())
.Where(method => method.ReturnType != typeof(void) && method.Name.StartsWith("Do"));
}
static void Prefix(out Stopwatch __state, MethodBase __originalMethod)
{
__state = Stopwatch.StartNew();
// ...
}
static void Postfix(Stopwatch __state, MethodBase __originalMethod)
{
__state.Stop();
// ....
}
}
The problem here is that the __originalMethod doesn't take care if it was called from A or B.
So for example, we had patched string.Join method. And the we call from A or B, where A or B, is the full callstack of this method.
So first, we need to assign a ID to this call, and we need to create a Tree-based structure (which is hard to serialize later), from here (https://stackoverflow.com/a/36649069/3286975):
public class TreeModel : Tree<TreeModel>
{
public int ID { get; set; }
public TreeModel() { }
public TreeModel(TreeModel parent) : base(parent) { }
}
public class Tree<T> where T : Tree<T>
{
protected Tree() : this(null) { }
protected Tree(T parent)
{
Parent=parent;
Children=new List<T>();
if(parent!=null)
{
parent.Children.Add(this as T);
}
}
public T Parent { get; set; }
public List<T> Children { get; set; }
public bool IsRoot { get { return Parent==null; } }
public T Root { get { return IsRoot?this as T:Parent.Root; } }
public T RecursiveFind(Predicate<T> check)
{
if(check(this as T)) return this as T;
foreach(var item in Children)
{
var result=item.RecursiveFind(check);
if(result!=null)
{
return result;
}
}
return null;
}
}
Now, the thing is that we need to fill the Tree as long as we iterate all the method and instructions got from Harmony. Forget about Harmony for a second, I will explain only two facts about it.
The lib allows you first to get all patched methods through IEnumerable<MethodBase> TargetMethods() so, you have the Assembly X passed through reflection and filtered all methods that are allowed to be patched (some of them broke Unity, so I decided to skip methods from UnityEngine., UnityEditor. and System.* namespaces).
And we have also the ReadMethodBody method (https://harmony.pardeike.net/api/HarmonyLib.PatchProcessor.html#HarmonyLib_PatchProcessor_ReadMethodBody_System_Reflection_MethodBase_) from a given MethodBase it returns all IL stack instructions.
So we can start to iterate over and over in order to get all instructions and fill the entire tree. This is what I wrote last night:
internal static class BenchmarkEnumerator
{
internal static Dictionary<MethodBase, int> Mappings { get; } = new Dictionary<MethodBase, int>();
internal static Dictionary<int, TreeModel> TreeIDs { get; } = new Dictionary<int, TreeModel>();
internal static Dictionary<MethodBase, BenchmarkTreeModel> TreeMappings { get; } = new Dictionary<MethodBase, BenchmarkTreeModel>();
private static HashSet<int> IDUsed { get; } = new HashSet<int>();
public static int GetID(this MethodBase method)
{
return GetID(method, out _);
}
public static int GetID(this MethodBase method, out bool contains)
{
// A > X = X1
// B > X = X2
if (!Mappings.ContainsKey(method))
{
var id = Mappings.Count;
Mappings.Add(method, Mappings.Count);
IDUsed.Add(id);
contains = false;
return id;
}
contains = true;
return Mappings[method];
}
public static int GetFreeID()
{
int id;
Random rnd = new Random();
do
{
id = rnd.Next();
} while (IDUsed.Contains(id));
IDUsed.Add(id);
return id;
}
public static BenchmarkCall GetCall(int id)
{
return TreeIDs[id]?.Call;
}
public static BenchmarkCall GetCall(this MethodBase method)
{
return TreeIDs[Mappings[method]]?.Call;
}
}
The BenchmarkEnumerator class allow us to differentiate between A or B, but it doesn't care about the full hierarchy, only from the parent MethodBase itself, so I need to write something complex to take in care of the full call stack, which I said I have a problem to understand.
Then we have the TargetMethods:
private static IEnumerable<MethodBase> TargetMethods()
{
Model = new BenchmarkTreeModel();
var sw = Stopwatch.StartNew();
//int i = 0;
return Filter.GetTargetMethods(method =>
{
try
{
var instructions = PatchProcessor.ReadMethodBody(method);
var i = method.GetID(out var contains);
var tree = new TreeModel
{
ID = i
};
if (contains)
{
//var lastId = i;
i = GetFreeID();
tree.ID = i;
tree.FillMethodName($"{method.GetMethodSignature()}_{i}"); // TODO: Check this
tree.Parent = null;
tree.Children = TreeMappings[method].Forest.First().Children; // ??
//DictionaryHelper.AddOrAppend(TreeMappings, method, tree);
TreeMappings[method].Forest.Add(tree);
TreeIDs.Add(i, tree);
Model.Forest.Add(tree);
// UNIT TESTING: All contained methods at this point will have a parent.
// string.Join is being added as a method by a instruction, so when we try to patch it, it will have already a reference on the dictionary
// Here, we check if the method was already added by a instruction CALL
// Logic: If the method is already contained by the mapping dictionary
// then, we will exit adding a new that will have the same childs but a new ID
return false;
}
TreeIDs.Add(i, tree);
tree.FillMethodName($"{method.GetMethodSignature()}_{i}"); // TODO: Check this
foreach (var pair in instructions)
{
var opcode = pair.Key;
if (opcode != OpCodes.Call || opcode != OpCodes.Callvirt) continue;
var childMethod = (MethodBase)pair.Value;
var id = childMethod.GetID(out var _contains);
var subTree = new TreeModel(tree)
{
ID = id
};
if (_contains)
{
id = GetFreeID();
subTree.ID = id;
subTree.FillMethodName($"{childMethod.GetMethodSignature()}_{id}"); // TODO: Check this
subTree.Parent = TreeIDs[i];
subTree.Children = TreeMappings[childMethod].Forest.First().Children;
TreeIDs.Add(id, subTree);
continue;
}
TreeIDs.Add(id, subTree);
subTree.FillMethodName($"{childMethod.GetMethodSignature()}_{id}");
tree.Children.Add(subTree);
TreeMappings.Add(childMethod, new BenchmarkTreeModel());
TreeMappings[childMethod].Forest.Add(subTree);
}
TreeMappings.Add(method, new BenchmarkTreeModel());
TreeMappings[method].Forest.Add(tree);
Model.Forest.Add(tree);
return true;
//var treeModel = new TreeModel();
}
catch (Exception ex)
{
//Debug.LogException(new Exception(method.GetMethodSignature(), ex));
return false;
}
}, sw);
//return methods;
}
The GetMethodSignature is something like:
public static string GetMethodSignature(this MethodBase method)
{
if (method == null) return null;
return method.DeclaringType == null ? method.Name : $"{method.DeclaringType.FullName}.{method.Name}";
}
I think I'll replace it with the MethodBase.ToString instead (what do you think?)
Also, we have the BenchmarkCall class which allow us to take in care how many times the call was done and how many time it has spent at all:
[Serializable]
public class BenchmarkCall
{
public string Method { get; set; }
public double SpentMilliseconds { get; set; }
public long SpentTicks { get; set; }
public double MinSpentMs { get; set; } = double.MaxValue;
public double MaxSpentMs { get; set; } = double.MinValue;
public long MinSpentTicks { get; set; } = long.MaxValue;
public long MaxSpentTicks { get; set; } = long.MinValue;
public double AvgMs => SpentMilliseconds / TimesCalled;
public double AvgTicks => SpentTicks / (double)TimesCalled;
public BenchmarkCall()
{
}
public BenchmarkCall(MethodBase method)
{
Method = method.GetMethodSignature();
}
public override string ToString()
{
if (TimesCalled > 0)
return "BenchmarkCall{\n" +
$"Ticks[SpentTicks={SpentTicks},MinTicks={MinSpentTicks},MaxTicks={MaxSpentTicks},AvgTicks={AvgTicks:F2}]\n" +
$"Ms[SpentMs={SpentMilliseconds:F2},MinMs={MinSpentMs:F2},MaxMs={MaxSpentMs:F2},AvgMs={AvgMs:F2}]\n" +
"}";
return "BenchmarkCall{}";
}
}
}
So I think that my next movement will be to differentiate between X method being called from A or B (Xa or Xb) taking care of the full hierarchy (which I'm not sure how to do) instead of the parent method that calls it, maybe the code I wrote has some to do it with it, but I'm not sure (last night I was so tired, so I didn't code it taking care those facts), build up a list of method signatures with different IDs, and then fill up the tree, ID 1 is Xa and ID 2 is Xb (where I have problems also filling up the tree).
Also I'll need to use the Transpiler in order to alter all code instructions, so if a method has:
void method() {
X1();
X2();
}
We will need to add 2 methods (like prefix/postfix) to measure each instruction call:
void method() {
Start(1);
X1();
End(1);
Start(2);
X2();
End(2);
}
This will be a hard task, but I hope somebody could guide me with this out.
I am currently working on a ASP.NET Web API 2 (C#) and have been using Swashbuckle without problems until today. I added a property to an existing model and broke the entire doc generation so whenever I go to /help/ui/index#!/ page I now see the following:
500 : {"message":"An error has occurred."}
The enum below is the new property that I added to the existing model. If I remove the short type inheritance, everything works fine. Any ideas of how I can use JsonConverter or a custom filter to make sure Swagger doesn't break?
public enum TestEnum: short
{
Unknown = 0,
Green = 1
}
This is the POST request:
[Route("{id:int:min(1)}/customers"), HttpPost]
public IHttpActionResult PostCustomer(int id, [FromBody] CustomerModel customerModel)
{
return Ok();
}
It fails when I add a new property to CustomerModel if and only if the Enum is of type short.
Thanks for the help!
I enabled a while back an IDocumentFilter that helped us display both integer and string values of an Enum to provide more useful details to our clients.
Issue was that this class was only casting Enums to INTs, so I had to update it to accept other types as well. Here is the class I'm using:
public class SwaggerEnumDescriptions : IDocumentFilter
{
public void Apply(SwaggerDocument swaggerDoc, SchemaRegistry schemaRegistry, IApiExplorer apiExplorer)
{
// add enum descriptions to result models
foreach (KeyValuePair<string, Schema> schemaDictionaryItem in swaggerDoc.definitions)
{
Schema schema = schemaDictionaryItem.Value;
foreach (KeyValuePair<string, Schema> propertyDictionaryItem in schema.properties)
{
Schema property = propertyDictionaryItem.Value;
IList<object> propertyEnums = property.#enum;
if (propertyEnums?.Count > 0)
{
property.description += $": {DescribeEnum(propertyEnums)}";
}
}
}
// add enum descriptions to input parameters
if (swaggerDoc.paths.Count > 0)
{
foreach (PathItem pathItem in swaggerDoc.paths.Values)
{
DescribeEnumParameters(pathItem.parameters);
// head, patch, options, delete left out
List<Operation> possibleParameterisedOperations = new List<Operation> { pathItem.get, pathItem.post, pathItem.put };
possibleParameterisedOperations.FindAll(x => x != null).ForEach(x => DescribeEnumParameters(x.parameters));
}
}
}
private void DescribeEnumParameters(IList<Parameter> parameters)
{
if (parameters != null)
{
foreach (Parameter param in parameters)
{
IList<object> paramEnums = param.#enum;
if (paramEnums?.Count > 0)
{
param.description += $": {DescribeEnum(paramEnums)}";
}
}
}
}
private string DescribeEnum(IList<object> enums)
{
List<string> enumDescriptions = new List<string>();
foreach (object enumOption in enums)
{
Type enumType = enumOption.GetType();
object enumValue =
Enum.GetUnderlyingType(enumType) == typeof(byte) ? (byte)enumOption :
Enum.GetUnderlyingType(enumType) == typeof(short) ? (short)enumOption :
Enum.GetUnderlyingType(enumType) == typeof(long) ? (long)enumOption :
(int)enumOption;
enumDescriptions.Add($"{enumValue} = {Enum.GetName(enumType, enumOption)}");
}
return string.Join(", ", enumDescriptions.ToArray());
}
}
In order to use this on your Api, you need to add the IDocumentFilter to Swagger Config:
c.DocumentFilter<SwaggerEnumDescriptions>();
Using .NET Core, I am trying to save and retrieve a JSON Array of the object from Redis using IDistributedCache. Below is my code for storing and reading from Redis cache:
public void Save(string key, object content, int duration)
{
string s;
if (content is string)
{
s = (string)content;
}
else
{
s = JsonConvert.SerializeObject(content);
}
duration = duration <= 0 ? DefaultCacheDuration : duration;
Cache.Set(key, Encoding.UTF8.GetBytes(s), new DistributedCacheEntryOptions()
{
AbsoluteExpiration = DateTime.Now + TimeSpan.FromSeconds(duration)
});
}
public T Get<T>(string key) where T : class
{
var c = Cache.Get(key);
if (c == null)
{
return null;
}
var str = Encoding.UTF8.GetString(c);
if (typeof(T) == typeof(string))
{
return str as T;
}
return JsonConvert.DeserializeObject<T>(str);
}
the object that I want to store is
public class RuleLoadCollection_Result
{
[Key]
public int RuleId { get; set; }
public string RuleName { get; set; }
}
In my biz logic, am saving the object like this
public IQueryable<RuleLoadCollection_Result> GetRuleLibrary()
{
var result = _dbClient.GetRuleLibrary();
_cache.Save("TestKey", result);
return result;
}
the output here is an Array of Object.
[{"ruleId":1,"ruleName":"a1"}]
What code should I write to return the same array of objects from cache? I tried a few options, most of them gave compile or runtime errors. After the bit of browsing, I tried below, it worked, but it is giving only the first element of the array.
public RuleLoadCollection_Result GetRuleLibraryFromCache()
{
return (_cache.Get<List<RuleLoadCollection_Result>>("TestKey").First());
}
output for this is
{"ruleId":1,"ruleName":"a1"}
which I understand why, but what c# should I write to JSON array back which I saved?
below code gives the runtime error
public IQueryable<RuleLoadCollection_Result> GetRuleLibraryFromCache()
{
return (_cache.Get<IQueryable<RuleLoadCollection_Result>>("TestKey"));
}
the runtime error is:
Cannot create and populate list type System.Linq.IQueryable`1[RuleLoadCollection_Result]. Path '', line 1, position 1.
This worked.
public IQueryable<RuleLoadCollection_Result> GetRuleLibraryFromCache()
{
var result = _cache.Get<IEnumerable<RuleLoadCollection_Result>>("TestKey").AsQueryable();
return result;
}
Frustrating, this. Here's a pair of related objects, as generated by database-first Entity Framework:
public partial class DevelopmentType
{
public DevelopmentType()
{
this.DefaultCharges = new HashSet<DefaultCharge>();
}
public System.Guid RowId { get; set; }
public string Type { get; set; }
public virtual ICollection<DefaultCharge> DefaultCharges { get; set; }
}
public partial class DefaultCharge
{
public System.Guid RowId { get; set; }
public decimal ChargeableRate { get; set; }
public Nullable<System.Guid> DevelopmentType_RowId { get; set; }
public virtual DevelopmentType DevelopmentType { get; set; }
}
Here's the code that I'm calling to save a DevelopmentType - it involves automapper since we differentiate entity objects from DTOs:
public void SaveDevelopmentType(DevelopmentType_dto dt)
{
Entities.DevelopmentType mappedDevType = Mapper.Map<DevelopmentType_dto, Entities.DevelopmentType>(dt);
_Context.Entry(mappedDevType).State = System.Data.EntityState.Modified;
_Context.DevelopmentTypes.Attach(mappedDevType);
_Context.SaveChanges();
}
In my user interface, the most common operation will be for a user to look at a list of DevelopmentTypes and update their DefaultCharge. So when I test this using the above code, it runs without error, but nothing actually changes.
If I pause in the debugger it's clear that the changed DefaultCharge is being passed into the function, and that it's attached to the DevelopmentType to be saved.
Stepping through it, if I change the value manually inside visual studio, it does save the updated value. Which is just even more confusing.
Monitoring the database with SQL Server Profiler reveals that update commands are issued only for the parent object and not for any attached objects.
I have other similar code elsewhere that functions as expected. What am I doing wrong here?
EDIT:
I have discovered that if you do this prior to the call to SaveDevelopmentType:
using (TransactionScope scope = new TransactionScope())
{
dt.Type = "Test1";
dt.DefaultCharges.First().ChargeableRate = 99;
_CILRepository.SaveDevelopmentType(dt);
scope.Complete();
}
The change to Type saves, but the change to ChargeableRate does not. I don't think it helps, massively, but thought I'd add it.
The problem is, that EF is not aware of the changed DefaultCharges.
By setting the State of the DevelopmentType to EntityState.Modified, EF only knows that the object DevelopmentType has been changed. However, this means that EF will only update DevelopmentType but not it's navigation properties.
A workaround - which isn't best practice - would be to iterate over all DefaultCharge of the current DevelopmentType and set the entity state to EntityState.Modified.
Additionally I would recommend to attach the entity to the context first, and change the state afterwards.
EDIT after comment
As you are using DTOs I suppose you are transfering these objects either through different layers or different machines.
In this case I would recommend to use self tracking entities, because it is not possible to share one context. These entities additionally holds their current state (ie. new, updated, deleted etc). There are many tutorials on the net about self tracking entities.
e.g. MSDN - Working with Self-Tracking Entities
As far as I know EF can save child entities only if the parent object was retrieved with the same Context that is trying to save it. That is attaching an object that was retrieved by one context to another context, will allow you to save changes to parent objects but not children. This was the result of a on old search based on which we switched to NHibernate. If memory serves correctly I was able to find a link where EF team member(s) confirmed this and that there WAS no plan to change this behavior. Unfortunately all links related to that search have been erased from my PC since.
As I am not aware of how you are retrieving the objects in your case, I am not sure this is relevant to your case, but put it out there just in case it helps.
Here is a link on attaching detached objects to a context.
http://www.codeproject.com/Articles/576330/Attaching-detached-POCO-to-EF-DbContext-simple-and
Context.Entry() already "Attaches" the Entity internally in order to have the context change its EntityState.
By calling Attach() you're changing the EntityState back to Unchanged. Try to comment out this line.
The Graphdiff library was a great help for me to handle all of these complexities.
You only need to set up the navigation properties that you wish to insert/update/delete (using fluent syntax) and Graphdiff will take care of it
Note: It seems to be that the project is not updated anymore but i'm using it since more than a year and is quite stable
This is not a workaround for every case, but I did discover that you can get around this by updating foreign keys on an object instead of updating navigation property objects.
For example... instead of:
myObject.myProperty = anotherPropertyObject;
Try this:
myObject.myPropertyID = anotherPropertyObject.ID;
Make sure the object is flagged as modified in EF's mind (as mentioned in other posts) and then call your save method.
Worked for me at least! It'll be a no-go when working with nested properties, but perhaps you can break your contexts up into smaller chunks and work over objects in multiple parts to avoid context bloat.
Good luck! :)
If I understand the question correctly, you have problem updating child fields. I had problems with child collection fields. I tried this and it worked for me.
You should update all child collections after attaching the object to the database context change the modified state of the parent object and save changes to the context.
Database.Products.Attach(argProduct);
argProduct.Categories = Database.Categories.Where(x => ListCategories.Contains(x.CategoryId)).ToList();
Database.Entry(argProduct).State = EntityState.Modified;
Database.SaveChanges();
I created a helper method to solve this problem.
Consider this:
public abstract class BaseEntity
{
/// <summary>
/// The unique identifier for this BaseEntity.
/// </summary>
[Key]
public Guid Id { get; set; }
}
public class BaseEntityComparer : IEqualityComparer<BaseEntity>
{
public bool Equals(BaseEntity left, BaseEntity right)
{
if (ReferenceEquals(null, right)) { return false; }
return ReferenceEquals(left, right) || left.Id.Equals(right.Id);
}
public int GetHashCode(BaseEntity obj)
{
return obj.Id.GetHashCode();
}
}
public class Event : BaseEntity
{
[Required(AllowEmptyStrings = false)]
[StringLength(256)]
public string Name { get; set; }
public HashSet<Manager> Managers { get; set; }
}
public class Manager : BaseEntity
{
[Required(AllowEmptyStrings = false)]
[StringLength(256)]
public string Name { get; set; }
public Event Event{ get; set; }
}
DbContext with the helper method:
public class MyDataContext : DbContext
{
public MyDataContext() : base("ConnectionName") { }
//Tables
public DbSet<Event> Events { get; set; }
public DbSet<Manager> Managers { get; set; }
public async Task AddOrUpdate<T>(T entity, params string[] ignoreProperties) where T : BaseEntity
{
if (entity == null || Entry(entity).State == EntityState.Added || Entry(entity).State == EntityState.Modified) { return; }
var state = await Set<T>().AnyAsync(x => x.Id == entity.Id) ? EntityState.Modified : EntityState.Added;
Entry(entity).State = state;
var type = typeof(T);
RelationshipManager relationship;
var stateManager = ((IObjectContextAdapter)this).ObjectContext.ObjectStateManager;
if (stateManager.TryGetRelationshipManager(entity, out relationship))
{
foreach (var end in relationship.GetAllRelatedEnds())
{
var isForeignKey = end.GetType().GetProperty("IsForeignKey", BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(end) as bool?;
var navigationProperty = end.GetType().GetProperty("NavigationProperty", BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(end);
var propertyName = navigationProperty?.GetType().GetProperty("Identity", BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(navigationProperty) as string;
if (string.IsNullOrWhiteSpace(propertyName) || ignoreProperties.Contains(propertyName)) { continue; }
var property = type.GetProperty(propertyName);
if (property == null) { continue; }
if (end is IEnumerable) { await UpdateChildrenInternal(entity, property, isForeignKey == true); }
else { await AddOrUpdateInternal(entity, property, ignoreProperties); }
}
}
if (state == EntityState.Modified)
{
Entry(entity).OriginalValues.SetValues(await Entry(entity).GetDatabaseValuesAsync());
Entry(entity).State = GetChangedProperties(Entry(entity)).Any() ? state : EntityState.Unchanged;
}
}
private async Task AddOrUpdateInternal<T>(T entity, PropertyInfo property, params string[] ignoreProperties)
{
var method = typeof(EasementDataContext).GetMethod("AddOrUpdate");
var generic = method.MakeGenericMethod(property.PropertyType);
await (Task)generic.Invoke(this, new[] { property.GetValue(entity), ignoreProperties });
}
private async Task UpdateChildrenInternal<T>(T entity, PropertyInfo property, bool isForeignKey)
{
var type = typeof(T);
var method = isForeignKey ? typeof(EasementDataContext).GetMethod("UpdateForeignChildren") : typeof(EasementDataContext).GetMethod("UpdateChildren");
var objType = property.PropertyType.GetGenericArguments()[0];
var enumerable = typeof(IEnumerable<>).MakeGenericType(objType);
var param = Expression.Parameter(type, "x");
var body = Expression.Property(param, property);
var lambda = Expression.Lambda(Expression.Convert(body, enumerable), property.Name, new[] { param });
var generic = method.MakeGenericMethod(type, objType);
await (Task)generic.Invoke(this, new object[] { entity, lambda, null });
}
public async Task UpdateForeignChildren<T, TProperty>(T parent, Expression<Func<T, IEnumerable<TProperty>>> childSelector, IEqualityComparer<TProperty> comparer = null) where T : BaseEntity where TProperty : BaseEntity
{
var children = (childSelector.Invoke(parent) ?? Enumerable.Empty<TProperty>()).ToList();
foreach (var child in children) { await AddOrUpdate(child); }
var existingChildren = await Set<T>().Where(x => x.Id == parent.Id).SelectMany(childSelector).AsNoTracking().ToListAsync();
if (comparer == null) { comparer = new BaseEntityComparer(); }
foreach (var child in existingChildren.Except(children, comparer)) { Entry(child).State = EntityState.Deleted; }
}
public async Task UpdateChildren<T, TProperty>(T parent, Expression<Func<T, IEnumerable<TProperty>>> childSelector, IEqualityComparer<TProperty> comparer = null) where T : BaseEntity where TProperty : BaseEntity
{
var stateManager = ((IObjectContextAdapter)this).ObjectContext.ObjectStateManager;
var currentChildren = childSelector.Invoke(parent) ?? Enumerable.Empty<TProperty>();
var existingChildren = await Set<T>().Where(x => x.Id == parent.Id).SelectMany(childSelector).AsNoTracking().ToListAsync();
if (comparer == null) { comparer = new BaseEntityComparer(); }
var addedChildren = currentChildren.Except(existingChildren, comparer).AsEnumerable();
var deletedChildren = existingChildren.Except(currentChildren, comparer).AsEnumerable();
foreach (var child in currentChildren) { await AddOrUpdate(child); }
foreach (var child in addedChildren) { stateManager.ChangeRelationshipState(parent, child, childSelector.Name, EntityState.Added); }
foreach (var child in deletedChildren)
{
Entry(child).State = EntityState.Unchanged;
stateManager.ChangeRelationshipState(parent, child, childSelector.Name, EntityState.Deleted);
}
}
public static IEnumerable<string> GetChangedProperties(DbEntityEntry dbEntry)
{
var propertyNames = dbEntry.State == EntityState.Added ? dbEntry.CurrentValues.PropertyNames : dbEntry.OriginalValues.PropertyNames;
foreach (var propertyName in propertyNames)
{
if (IsValueChanged(dbEntry, propertyName))
{
yield return propertyName;
}
}
}
private static bool IsValueChanged(DbEntityEntry dbEntry, string propertyName)
{
return !Equals(OriginalValue(dbEntry, propertyName), CurrentValue(dbEntry, propertyName));
}
private static string OriginalValue(DbEntityEntry dbEntry, string propertyName)
{
string originalValue = null;
if (dbEntry.State == EntityState.Modified)
{
originalValue = dbEntry.OriginalValues.GetValue<object>(propertyName) == null
? null
: dbEntry.OriginalValues.GetValue<object>(propertyName).ToString();
}
return originalValue;
}
private static string CurrentValue(DbEntityEntry dbEntry, string propertyName)
{
string newValue;
try
{
newValue = dbEntry.CurrentValues.GetValue<object>(propertyName) == null
? null
: dbEntry.CurrentValues.GetValue<object>(propertyName).ToString();
}
catch (InvalidOperationException) // It will be invalid operation when its in deleted state. in that case, new value should be null
{
newValue = null;
}
return newValue;
}
}
Then I call it like this
// POST: Admin/Events/Edit/5
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Edit(Event #event)
{
if (!ModelState.IsValid) { return View(#event); }
await _db.AddOrUpdate(#event);
await _db.SaveChangesAsync();
return RedirectToAction("Index");
}
I'm trying to return a different provider, depending on a custom context. Given the following
public interface IProvider
{
string WhoAreYou();
}
And two providers
namespace ProviderOne
{
public class Implementation : IProvider
{
public string WhoAreYou()
{
return "Provider One";
}
}
}
namespace ProviderTwo
{
public class Implementation : IProvider
{
public string WhoAreYou()
{
return "Provider Two";
}
}
}
And a context as follows
public class CallContext
{
public string SomeValue{ get; set; }
public int AnotherValue { get; set; }
}
My binding looks like this
CallContext context1 = new CallContext()
{
SomeValue = "one",
AnotherValue = 1
};
Bind<IProvider>().To<ProviderOne.Implementation>().WithMetadata("callcontext", context1);
CallContext context2 = new CallContext()
{
SomeValue = "two",
AnotherValue = 2
};
Bind<IProvider>().To<ProviderOne.Implementation>().WithMetadata("callcontext", context2);
I'm fairly certain it is correct up to here, though it's late, and I'm out of ideas.
My problem is getting to those bindings. I have tried various methods
var test1 = kernel.Get<IProvider>(b => b.Get<CallContext>("callcontext") == context1);
//var test1 = kernel.Get<IProvider>(m => m.Has("callcontext") && m.Get<CallContext>("callcontext").Equals(context1));
//var test1 = kernel.Get<IProvider>(m => m.Get<CallContext>("callcontext").Equals(context1));
//var test1 = kernel.Get<IProvider>().Equals(context1);
But they don't work, at best I get the "No binding" error, at worst I just get errors. I'm sure there must be something easy I am overlooking, or just no aware off.
Thank you
I found the problem, and if anyone runs up against this in the future, the solution above is fine, except you have to explicit override Equals for Ninject to resolve to the object. As follows.
public override bool Equals(object obj)
{
if (obj.GetType() == typeof(CallContext))
{
var context = (CallContext)obj;
if (Country != null)
{
if (Country.ToLower() != context.Country.ToLower())
return false;
}
if (Store != context.Store)
return false;
return true;
}
return false;
}
Then you can resolve it as proposed.
CallContext context1 = new CallContext()
{
SomeValue = "one",
AnotherValue = 1
};
var test1 = kernel.Get<IProvider>(b => b.Get<CallContext>("callcontext").Equals(context1));