How do I get the old values of an entity?
follows the example..
public void Update(User user)
ValidateEntity(user, OperationType.Update);
oldUser = (how do I get the old values (database) of the entity User?)
Set.Attach(user);
Context.ObjectStateManager.ChangeObjectState(user, EntityState.Modified);
Context.SaveChanges();
OnUpdated(user, oldUser);
}
Try this:
public void Update(User user)
ValidateEntity(user, OperationType.Update);
var oldUser = Set.Single(u => u.Id == user.Id);
Context.Detach(oldUser);
Set.Attach(user);
Context.ObjectStateManager.ChangeObjectState(user, EntityState.Modified);
Context.SaveChanges();
OnUpdated(user, oldUser);
}
Or this:
public void Update(User user)
{
ValidateEntity(user, OperationType.Update);
var oldUser = Set.Single(u => u.Id == user.Id);
Set.ApplyCurrentValues(user);
Context.SaveChanges(SaveOptions.DetectChangesBeforeSave);
OnUpdated(user, Context.ObjectStateManager.GetOjectStateEntry(user).OriginalValues);
Context.AcceptAllChanges();
}
I found one way of convert DbDataRecord to entity type using reflection...
where http://www.instanceofanobject.com/2011/01/ef4-dbdatarecord-convertto.html
public static class AnonymousTypeConversion
{
///
/// Converts a single DbDataRwcord object into something else.
/// The destination type must have a default constructor.
///
///
///
///
public static T ConvertTo(this DbDataRecord record)
{
T item = Activator.CreateInstance();
for (int f = 0; f
/// Converts a list of DbDataRecord to a list of something else.
///
///
///
///
public static List ConvertTo(this List list)
{
List result = (List)Activator.CreateInstance>();
list.ForEach(rec =>
{
result.Add(rec.ConvertTo());
});
return result;
}
}
Related
How should I create PropertyAttribute and PropertyDrawer to show ScriptableObjects in dropdown menu in Inspector for multi selecting ?
I've published a repository on Github which solve this problem. It is for multi selecting in dropdown menu in Inspector.
In Github links you have access to example folder and unitypackage in release page but if you don't want to go to the links or any problem happens to the links, you can follow this instruction:
ScriptableObjectMultiSelectDropdown:
ScriptableObjectMultiSelectDropdown is an attribute for the Unity Inspector.
It is used for showing ScriptableObjects which are created in your project, in dropdown menu and select multiple of them in Inspector.
Code:
ScriptableObjectReference.cs:
// Copyright (c) ATHellboy (Alireza Tarahomi) Limited. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root.
using System;
using UnityEngine;
namespace ScriptableObjectMultiSelectDropdown
{
/// <summary>
/// Because you can't make a PropertyDrawer for arrays or generic lists themselves,
/// I had to create parent class as an abstract layer.
/// </summary>
[Serializable]
public class ScriptableObjectReference
{
public ScriptableObject[] values;
}
}
ScriptableObjectMultiSelectDropdownAttribute.cs:
// Copyright (c) ATHellboy (Alireza Tarahomi) Limited. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root.
using System;
using UnityEngine;
namespace ScriptableObjectMultiSelectDropdown
{
/// <summary>
/// Indicates how selectable scriptableObjects should be collated in drop-down menu.
/// </summary>
public enum ScriptableObjectGrouping
{
/// <summary>
/// No grouping, just show type names in a list; for instance, "MainFolder > NestedFolder > SpecialScriptableObject".
/// </summary>
None,
/// <summary>
/// Group classes by namespace and show foldout menus for nested namespaces; for
/// instance, "MainFolder >> NestedFolder >> SpecialScriptableObject".
/// </summary>
ByFolder,
/// <summary>
/// Group scriptableObjects by folder; for instance, "MainFolder > NestedFolder >> SpecialScriptableObject".
/// </summary>
ByFolderFlat
}
/// <example>
/// <para>Usage Examples</para>
/// <code language="csharp"><![CDATA[
/// using UnityEngine;
/// using ScriptableObjectDropdown;
///
/// [CreateAssetMenu(menuName = "Create Block")]
/// public class Block : ScriptableObject
/// {
/// // Some fields
/// }
///
/// public class BlockManager : MonoBehaviour
/// {
/// [ScriptableObjectMultiSelectDropdown(typeof(Block))]
/// public ScriptableObjectReference firstTargetBlocks;
///
/// // or
///
/// [ScriptableObjectMultiSelectDropdown(typeof(Block), grouping = ScriptableObjectGrouping.ByFolder)]
/// public ScriptableObjectReference secondTargetBlocks;
/// }
///
/// // or
///
/// [CreateAssetMenu(menuName = "Create Block Manager Settings")]
/// public class BlockManagerSetting : ScriptableObject
/// {
/// [ScriptableObjectMultiSelectDropdown(typeof(Block))]
/// public ScriptableObjectReference firstTargetBlocks;
///
/// // or
///
/// [ScriptableObjectMultiSelectDropdown(typeof(Block), grouping = ScriptableObjectGrouping.ByFolderFlat)]
/// public ScriptableObjectReference secondTargetBlocks;
/// }
/// ]]></code>
/// </example>
[AttributeUsage(AttributeTargets.Field, AllowMultiple = false)]
public class ScriptableObjectMultiSelectDropdownAttribute : PropertyAttribute
{
public ScriptableObjectGrouping grouping = ScriptableObjectGrouping.None;
private Type _baseType;
public Type BaseType
{
get { return _baseType; }
private set { _baseType = value; }
}
public ScriptableObjectMultiSelectDropdownAttribute(Type baseType)
{
_baseType = baseType;
}
}
}
Put this one in Editor folder:
ScriptableObjectMultiSelectionDropdownDrawer.cs:
// Copyright (c) ATHellboy (Alireza Tarahomi) Limited. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root.
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using System.Reflection;
using System.Linq;
namespace ScriptableObjectMultiSelectDropdown.Editor
{
// TODO: Mixed value (-) for selecting multi objects
[CustomPropertyDrawer(typeof(ScriptableObjectReference))]
[CustomPropertyDrawer(typeof(ScriptableObjectMultiSelectDropdownAttribute))]
public class ScriptableObjectMultiSelectionDropdownDrawer : PropertyDrawer
{
private static ScriptableObjectMultiSelectDropdownAttribute _attribute;
private static List<ScriptableObject> _scriptableObjects = new List<ScriptableObject>();
private static List<ScriptableObject> _selectedScriptableObjects = new List<ScriptableObject>();
private static readonly int _controlHint = typeof(ScriptableObjectMultiSelectDropdownAttribute).GetHashCode();
private static GUIContent _popupContent = new GUIContent();
private static int _selectionControlID;
private static readonly GenericMenu.MenuFunction2 _onSelectedScriptableObject = OnSelectedScriptableObject;
private static bool isChanged;
static ScriptableObjectMultiSelectionDropdownDrawer()
{
EditorApplication.projectChanged += ClearCache;
}
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
ScriptableObjectMultiSelectDropdownAttribute castedAttribute = attribute as ScriptableObjectMultiSelectDropdownAttribute;
if (_scriptableObjects.Count == 0)
{
GetScriptableObjects(castedAttribute);
}
Draw(position, label, property, castedAttribute);
}
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
{
return EditorStyles.popup.CalcHeight(GUIContent.none, 0);
}
/// <summary>
/// How you can get type of field which it uses PropertyAttribute
/// </summary>
private static Type GetPropertyType(SerializedProperty property)
{
Type parentType = property.serializedObject.targetObject.GetType();
FieldInfo fieldInfo = parentType.GetField(property.propertyPath);
if (fieldInfo != null)
{
return fieldInfo.FieldType;
}
return null;
}
private static bool ValidateProperty(SerializedProperty property)
{
Type propertyType = GetPropertyType(property);
if (propertyType == null)
{
return false;
}
if (propertyType != typeof(ScriptableObjectReference))
{
return false;
}
return true;
}
/// <summary>
/// When new ScriptableObject added to the project
/// </summary>
private static void ClearCache()
{
_scriptableObjects.Clear();
}
/// <summary>
/// Gets ScriptableObjects just when it is a first time or new ScriptableObject added to the project
/// </summary>
private static void GetScriptableObjects(ScriptableObjectMultiSelectDropdownAttribute attribute)
{
string[] guids = AssetDatabase.FindAssets(String.Format("t:{0}", attribute.BaseType));
for (int i = 0; i < guids.Length; i++)
{
_scriptableObjects.Add(AssetDatabase.LoadAssetAtPath(AssetDatabase.GUIDToAssetPath(guids[i]), attribute.BaseType) as ScriptableObject);
}
}
/// <summary>
/// Checks if the ScriptableObject is selected or not by checking if the list contains it.
/// </summary>
private static bool ResolveSelectedScriptableObject(ScriptableObject scriptableObject)
{
if (_selectedScriptableObjects == null)
{
return false;
}
return _selectedScriptableObjects.Contains(scriptableObject);
}
private static void Draw(Rect position, GUIContent label,
SerializedProperty property, ScriptableObjectMultiSelectDropdownAttribute attribute)
{
if (label != null && label != GUIContent.none)
position = EditorGUI.PrefixLabel(position, label);
if (ValidateProperty(property))
{
if (_scriptableObjects.Count != 0)
{
UpdateScriptableObjectSelectionControl(position, label, property.FindPropertyRelative("values"), attribute);
}
else
{
EditorGUI.LabelField(position, "There is no this type asset in the project");
}
}
else
{
EditorGUI.LabelField(position, "Use it with ScriptableObjectReference");
}
}
/// <summary>
/// Iterats through the property for finding selected ScriptableObjects
/// </summary>
private static ScriptableObject[] Read(SerializedProperty property)
{
List<ScriptableObject> selectedScriptableObjects = new List<ScriptableObject>();
SerializedProperty iterator = property.Copy();
SerializedProperty end = iterator.GetEndProperty();
while (!SerializedProperty.EqualContents(iterator, end) && iterator.Next(true))
{
if (iterator.propertyType == SerializedPropertyType.ObjectReference)
{
selectedScriptableObjects.Add(iterator.objectReferenceValue as ScriptableObject);
}
}
return selectedScriptableObjects.ToArray();
}
/// <summary>
/// Iterats through the property for storing selected ScriptableObjects
/// </summary>
private static void Write(SerializedProperty property, ScriptableObject[] scriptableObjects)
{
// Faster way
// var w = new System.Diagnostics.Stopwatch();
// w.Start();
int i = 0;
SerializedProperty iterator = property.Copy();
iterator.arraySize = scriptableObjects.Length;
SerializedProperty end = iterator.GetEndProperty();
while (!SerializedProperty.EqualContents(iterator, end) && iterator.Next(true))
{
if (iterator.propertyType == SerializedPropertyType.ObjectReference)
{
iterator.objectReferenceValue = scriptableObjects[i];
i++;
}
}
// w.Stop();
// long milliseconds = w.ElapsedMilliseconds;
// Debug.Log(w.Elapsed.TotalMilliseconds + " ms");
// Another way
// property.arraySize = scriptableObjects.Length;
// for (int i = 0; i < property.arraySize; i++)
// {
// property.GetArrayElementAtIndex(i).objectReferenceValue = scriptableObjects[i];
// }
}
private static void UpdateScriptableObjectSelectionControl(Rect position, GUIContent label,
SerializedProperty property, ScriptableObjectMultiSelectDropdownAttribute attribute)
{
ScriptableObject[] output = DrawScriptableObjectSelectionControl(position, label, Read(property), property, attribute);
if (isChanged)
{
isChanged = false;
Write(property, output);
}
}
private static ScriptableObject[] DrawScriptableObjectSelectionControl(Rect position, GUIContent label,
ScriptableObject[] scriptableObjects, SerializedProperty property, ScriptableObjectMultiSelectDropdownAttribute attribute)
{
bool triggerDropDown = false;
int controlID = GUIUtility.GetControlID(_controlHint, FocusType.Keyboard, position);
switch (Event.current.GetTypeForControl(controlID))
{
case EventType.ExecuteCommand:
if (Event.current.commandName == "ScriptableObjectReferenceUpdated")
{
if (_selectionControlID == controlID)
{
if (scriptableObjects != _selectedScriptableObjects.ToArray())
{
scriptableObjects = _selectedScriptableObjects.ToArray();
isChanged = true;
}
_selectionControlID = 0;
_selectedScriptableObjects = null;
}
}
break;
case EventType.MouseDown:
if (GUI.enabled && position.Contains(Event.current.mousePosition))
{
GUIUtility.keyboardControl = controlID;
triggerDropDown = true;
Event.current.Use();
}
break;
case EventType.KeyDown:
if (GUI.enabled && GUIUtility.keyboardControl == controlID)
{
if (Event.current.keyCode == KeyCode.Return || Event.current.keyCode == KeyCode.Space)
{
triggerDropDown = true;
Event.current.Use();
}
}
break;
case EventType.Repaint:
if (scriptableObjects.Length == 0)
{
_popupContent.text = "Nothing";
}
else if (scriptableObjects.Length == _scriptableObjects.Count)
{
_popupContent.text = "Everything";
}
else if (scriptableObjects.Length >= 2)
{
_popupContent.text = "Mixed ...";
}
else
{
_popupContent.text = scriptableObjects[0].name;
}
EditorStyles.popup.Draw(position, _popupContent, controlID);
break;
}
if (triggerDropDown)
{
_selectionControlID = controlID;
_selectedScriptableObjects = scriptableObjects.ToList();
DisplayDropDown(position, scriptableObjects, attribute.grouping);
}
return scriptableObjects;
}
private static void DisplayDropDown(Rect position, ScriptableObject[] selectedScriptableObject, ScriptableObjectGrouping grouping)
{
var menu = new GenericMenu();
menu.AddItem(new GUIContent("Nothing"), selectedScriptableObject.Length == 0, _onSelectedScriptableObject, null);
menu.AddItem(new GUIContent("Everything"),
(_scriptableObjects.Count != 0 && selectedScriptableObject.Length == _scriptableObjects.Count),
_onSelectedScriptableObject, _scriptableObjects.ToArray());
for (int i = 0; i < _scriptableObjects.Count; ++i)
{
var scriptableObject = _scriptableObjects[i];
string menuLabel = MakeDropDownGroup(scriptableObject, grouping);
if (string.IsNullOrEmpty(menuLabel))
continue;
var content = new GUIContent(menuLabel);
menu.AddItem(content, ResolveSelectedScriptableObject(scriptableObject), _onSelectedScriptableObject, scriptableObject);
}
menu.DropDown(position);
}
private static void OnSelectedScriptableObject(object userData)
{
if (userData == null)
{
_selectedScriptableObjects.Clear();
}
else if (userData.GetType().IsArray)
{
_selectedScriptableObjects = (userData as ScriptableObject[]).ToList();
}
else
{
ScriptableObject scriptableObject = userData as ScriptableObject;
if (!ResolveSelectedScriptableObject(scriptableObject))
{
_selectedScriptableObjects.Add(scriptableObject);
}
else
{
_selectedScriptableObjects.Remove(scriptableObject);
}
}
var scriptableObjectReferenceUpdatedEvent = EditorGUIUtility.CommandEvent("ScriptableObjectReferenceUpdated");
EditorWindow.focusedWindow.SendEvent(scriptableObjectReferenceUpdatedEvent);
}
private static string FindScriptableObjectFolderPath(ScriptableObject scriptableObject)
{
string path = AssetDatabase.GetAssetPath(scriptableObject);
path = path.Replace("Assets/", "");
path = path.Replace(".asset", "");
return path;
}
private static string MakeDropDownGroup(ScriptableObject scriptableObject, ScriptableObjectGrouping grouping)
{
string path = FindScriptableObjectFolderPath(scriptableObject);
switch (grouping)
{
default:
case ScriptableObjectGrouping.None:
path = path.Replace("/", " > ");
return path;
case ScriptableObjectGrouping.ByFolder:
return path;
case ScriptableObjectGrouping.ByFolderFlat:
int last = path.LastIndexOf('/');
string part1 = path.Substring(0, last);
string part2 = path.Substring(last);
path = part1.Replace("/", " > ") + part2;
return path;
}
}
}
}
Usage Example:
Create ScriptableObject class which you want to create specified objects by that.
using UnityEngine;
[CreateAssetMenu(menuName = "Create Block")]
public class Block : ScriptableObject
{
// Some fields
}
Create ScriptableObjects in the project.
Use ScriptableObjectMultiSelectDropdown attribute by setting type of specified ScriptableObject derived class and optional grouping (Default grouping is None) like this in MonoBeahviour or ScriptableObject derived classes.
MonoBehavior:
using ScriptableObjectMultiSelectDropdown;
using UnityEngine;
public class BlockManager : MonoBehaviour
{
// Without grouping (default is None)
[ScriptableObjectMultiSelectDropdown(typeof(Block))]
public ScriptableObjectReference firstTargetBlocks;
// By grouping
[ScriptableObjectMultiSelectDropdown(typeof(Block), grouping = ScriptableObjectGrouping.ByFolder)]
public ScriptableObjectReference secondTargetBlocks;
}
ScriptableObject:
using UnityEngine;
using ScriptableObjectMultiSelectDropdown;
[CreateAssetMenu(menuName = "Create Block Manager Settings")]
public class BlockManagerSettings : ScriptableObject
{
// Without grouping (default is None)
[ScriptableObjectMultiSelectDropdown(typeof(Block))]
public ScriptableObjectReference firstTargetBlocks;
// By grouping
[ScriptableObjectMultiSelectDropdown(typeof(Block), grouping = ScriptableObjectGrouping.ByFolderFlat)]
public ScriptableObjectReference secondTargetBlocks;
}
When I remove objects from the list the UI doesn't reflect the current state of the list, normally failling by only one item.
Example: If I remove 4 items, on the UI only shows that I removed 3 items
private ObservableCollection<Card> _cards;
public ObservableCollection<Card> Cards
{
get
{
if (_cards == null)
{
_cards = new ObservableCollection<Card>();
return _cards;
}
return _cards;
}
set
{
SetValue(ref _cards, value);
}
}
My remove method
private void RemoveFromCards(Card card)
{
for (int i = Cards.Count - 1; i >= 0; i--)
{
if (Cards[i].Id == card.Id)
{
Cards.RemoveAt(i);
/* I tried this but doesn't work also
*
*
ObservableCollection<Card> copy = Cards;
copy.RemoveAt(i);
Cards = copy;
*/
}
}
}
Calling remove method
private void RemoveCardsFromView(List<Archive> cards)
{
foreach (Archive a in cards)
{
Card c = new Card {Id = a.CardId};
RemoveFromCards(c);
}
}
Calling code
public async Task RefreshCardsView()
{
if (!CrossConnectivity.Current.IsConnected)
{
BuildToast(false,"No internet connection");
return;
}
try
{
JsonResult cards = (JsonResult) await HttpMiddleman.GetCards();
RemoveCardsFromView(cards.Data.DeletedCards);
AddCardsToView(cards.Data.AddedCards);
}
catch (Exception e)
{
Debug.WriteLine(e.Message);
BuildToast(false,"Could not refresh");
}
}
My binding in FlowListView
<controls:FlowListView x:Name="Board" HasUnevenRows="True"
BackgroundColor="Black"
FlowColumnCount="2"
FlowItemTappedCommand="{Binding ExecuteActionCommand}"
FlowColumnMinWidth="110"
IsPullToRefreshEnabled="True"
IsRefreshing="{Binding IsRefreshing}"
RefreshCommand="{Binding RefreshViewCommand}"
FlowItemsSource="{Binding Cards}"> .....
Thanks in advance guys
make sure that you fire correctly OnPropertyChanged after SetValue() in property statement
and
try with this class (from xamarin) and pass ienumerable of card instead card only object.
Example
private void RemoveFromCards(Card card)
{
Cards.RemoveRange(new []{card});
}
//or directly from
private void RemoveCardsFromView(List<Archive> cards)
{
Cards.RemoveRange(cards.Select(s=> new Card {Id = a.CardId}));
}
/// <summary>
/// Represents a dynamic data collection that provides notifications when items get added, removed, or when the whole list is refreshed.
/// </summary>
/// <typeparam name="T"></typeparam>
public class ObservableRangeCollection<T> : ObservableCollection<T>
{
/// <summary>
/// Initializes a new instance of the System.Collections.ObjectModel.ObservableCollection(Of T) class.
/// </summary>
public ObservableRangeCollection()
: base()
{
}
/// <summary>
/// Initializes a new instance of the System.Collections.ObjectModel.ObservableCollection(Of T) class that contains elements copied from the specified collection.
/// </summary>
/// <param name="collection">collection: The collection from which the elements are copied.</param>
/// <exception cref="System.ArgumentNullException">The collection parameter cannot be null.</exception>
public ObservableRangeCollection(IEnumerable<T> collection)
: base(collection)
{
}
/// <summary>
/// Adds the elements of the specified collection to the end of the ObservableCollection(Of T).
/// </summary>
public void AddRange(IEnumerable<T> collection, NotifyCollectionChangedAction notificationMode = NotifyCollectionChangedAction.Add)
{
if (collection == null)
throw new ArgumentNullException("collection");
CheckReentrancy();
if (notificationMode == NotifyCollectionChangedAction.Reset)
{
foreach (var i in collection)
{
Items.Add(i);
}
OnPropertyChanged(new PropertyChangedEventArgs("Count"));
OnPropertyChanged(new PropertyChangedEventArgs("Item[]"));
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
return;
}
int startIndex = Count;
var changedItems = collection is List<T> ? (List<T>)collection : new List<T>(collection);
foreach (var i in changedItems)
{
Items.Add(i);
}
OnPropertyChanged(new PropertyChangedEventArgs("Count"));
OnPropertyChanged(new PropertyChangedEventArgs("Item[]"));
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, changedItems, startIndex));
}
/// <summary>
/// Removes the first occurence of each item in the specified collection from ObservableCollection(Of T).
/// </summary>
public void RemoveRange(IEnumerable<T> collection)
{
if (collection == null)
throw new ArgumentNullException("collection");
foreach (var i in collection)
Items.Remove(i);
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
/// <summary>
/// Clears the current collection and replaces it with the specified item.
/// </summary>
public void Replace(T item)
{
ReplaceRange(new T[] { item });
}
/// <summary>
/// Clears the current collection and replaces it with the specified collection.
/// </summary>
public void ReplaceRange(IEnumerable<T> collection)
{
if (collection == null)
throw new ArgumentNullException("collection");
Items.Clear();
AddRange(collection, NotifyCollectionChangedAction.Reset);
}
}
Despite the advice to pass dependencies through the constructor I've found that the development cost of having parameterless constructors and then autowiring all of the properties on everything is significantly less and makes the application much easier to develop out and maintain. However sometimes (on a view model for example) I have a property that is registered with the container, but that I don't want to populate at construction (for example the selected item bound to a container).
Is there any way to tell the container to ignore certain properties when it autowires the rest?
At the moment I'm just resetting the properties marked with an attribute in the on activated event a la:
public static IRegistrationBuilder<TLimit, ScanningActivatorData, TRegistrationStyle>
PropertiesAutowiredExtended<TLimit, TRegistrationStyle>(
this IRegistrationBuilder<TLimit, ScanningActivatorData, TRegistrationStyle> builder)
{
builder.ActivatorData.ConfigurationActions.Add(
(type, innerBuilder) =>
{
var parameter = Expression.Parameter(typeof(object));
var cast = Expression.Convert(parameter, type);
var assignments = type.GetProperties()
.Where(candidate => candidate.HasAttribute<NotAutowiredAttribute>())
.Select(property => new { Property = property, Expression = Expression.Property(cast, property) })
.Select(data => Expression.Assign(data.Expression, Expression.Default(data.Property.PropertyType)))
.Cast<Expression>()
.ToArray();
if (assignments.Any())
{
var #action = Expression
.Lambda<Action<object>>(Expression.Block(assignments), parameter)
.Compile();
innerBuilder.OnActivated(e =>
{
e.Context.InjectUnsetProperties(e.Instance);
#action(e.Instance);
});
}
else
{
innerBuilder.OnActivated(e => e.Context.InjectUnsetProperties(e.Instance));
}
});
return builder;
}
Is there a better way to do this?
Not sure that this is a better one, but you can go from another side, register only needed properties via WithProperty syntax. Pros is that Autofac doesn't resolve unnecessary services. Here's a working example:
public class MyClass
{
public MyDependency MyDependency { get; set; }
public MyDependency MyExcludeDependency { get; set; }
}
public class MyDependency {}
public class Program
{
public static void Main(string[] args)
{
var builder = new ContainerBuilder();
builder.RegisterType<MyDependency>();
builder.RegisterType<MyClass>().WithPropertiesAutowiredExcept("MyExcludeDependency");
using (var container = builder.Build())
{
var myClass = container.Resolve<MyClass>();
Console.WriteLine(myClass.MyDependency == null);
Console.WriteLine(myClass.MyExcludeDependency == null);
}
}
}
public static class PropertiesAutowiredExtensions
{
// Extension that registers only needed properties
// Filters by property name for simplicity
public static IRegistrationBuilder<TLimit, TReflectionActivatorData, TRegistrationStyle>
WithPropertiesAutowiredExcept<TLimit, TReflectionActivatorData, TRegistrationStyle>(
this IRegistrationBuilder<TLimit, TReflectionActivatorData, TRegistrationStyle> registrationBuilder,
params string[] propertiesNames)
where TReflectionActivatorData : ReflectionActivatorData
{
var type = ((IServiceWithType)registrationBuilder.RegistrationData.Services.Single()).ServiceType;
foreach (var property in type
.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Where(pi => pi.CanWrite && !propertiesNames.Contains(pi.Name)))
{
// There's no additional checks like in PropertiesAutowired for simplicity
// You can add them from Autofac.Core.Activators.Reflection.AutowiringPropertyInjector.InjectProperties
var localProperty = property;
registrationBuilder.WithProperty(
new ResolvedParameter(
(pi, c) =>
{
PropertyInfo prop;
return pi.TryGetDeclaringProperty(out prop) &&
prop.Name == localProperty.Name;
},
(pi, c) => c.Resolve(localProperty.PropertyType)));
}
return registrationBuilder;
}
// From Autofac.Util.ReflectionExtensions
public static bool TryGetDeclaringProperty(this ParameterInfo pi, out PropertyInfo prop)
{
var mi = pi.Member as MethodInfo;
if (mi != null && mi.IsSpecialName && mi.Name.StartsWith("set_", StringComparison.Ordinal)
&& mi.DeclaringType != null)
{
prop = mi.DeclaringType.GetProperty(mi.Name.Substring(4));
return true;
}
prop = null;
return false;
}
}
I have a scenario where I am using Entity Framework in a WCF service, and changes happen on a non-tracked instance of a type that is mapped back to the database via code-first (non-trivial updates and deletes throughout the instance's object tree). When I try to attach the non-tracked instance into the context, EF is only recognizing changes to the simple value types on the root object.
Does anyone know of an elegant solution for this scenario? I am looking for a way to do this by using a generic repository, and avoiding having to run through the instance's entire object tree managing the "attach/detach" state of every object. I have considered possibly using ValueInjecter or AutoMapper to run the changes on a fully hydrated and tracked instance of the "old" state in order for the context to pickup the changes. Also, how would Nhibernate handle this situation?
Thanks in advance for your input!
UPDATE (7/31/2012): I have updated the code to handle genericly-typed keys, and some typing issues with EF Proxies. Also added some helper extensions when dealing with IEntity types. This implementation isn't perfect, but it is very functional.
UPDATE (3/13/2012): I have added a feature request for cleaner merging in EF. The request is located here: http://data.uservoice.com/forums/72025-ado-net-entity-framework-ef-feature-suggestions/suggestions/2679160-better-merging-change-tracking
UPDATE (3/12/2012): I have posted my solution below. It uses FubuCore, ValueInjecter, and requires entities to be marked with one of two interfaces, either IEntity, or IRecursiveEntity for recursive classes. The solution will handle recursive, self-linked entities.
Also, I am referencing a generic repository (Repository) that allows me to get a reference to the IDbSet that EF exposes. This could be substituded with any other generic or specific repository. Lastly, the IEntity interface uses an int? id, however you could define that however you want (Guid/Guid?). The solution itself isn't quite as elegant as I would like, however it allows for much more elegant data access code when behind a physical WCF service boundary.
public class DomainMergeInjection : ConventionInjection
{
private readonly Repository _repository;
private readonly Dictionary<string, object> _potentialParentObjectDump;
private readonly Cache<Type, Type> _entityTypesAndKeysCache;
public DomainMergeInjection(Repository repository)
{
_repository = repository;
_potentialParentObjectDump = new Dictionary<string, object>();
_entityTypesAndKeysCache = new Cache<Type, Type>();
}
protected override bool Match(ConventionInfo c)
{
return c.SourceProp.Name == c.TargetProp.Name;
}
protected override object SetValue(ConventionInfo c)
{
if(c.SourceProp.Value == null)
return null;
//for value types and string just return the value as is
if(c.SourceProp.Type.IsSimple())
return c.SourceProp.Value;
//TODO: Expand on this to handle IList/IEnumerable (i.e. the non-generic collections and arrays).
//handle arrays
if(c.SourceProp.Type.IsArray)
{
var sourceArray = c.SourceProp.Value as Array;
// ReSharper disable PossibleNullReferenceException
var clonedArray = sourceArray.Clone() as Array;
// ReSharper restore PossibleNullReferenceException
for(int index = 0; index < sourceArray.Length; index++)
{
var sourceValueAtIndex = sourceArray.GetValue(index);
//Skip null and simple values that would have already been moved in the clone.
if(sourceValueAtIndex == null || sourceValueAtIndex.GetType().IsSimple())
continue;
// ReSharper disable PossibleNullReferenceException
clonedArray.SetValue(RetrieveComplexSourceValue(sourceValueAtIndex), index);
// ReSharper restore PossibleNullReferenceException
}
return clonedArray;
}
//handle IEnumerable<> also ICollection<> IList<> List<>
if(c.SourceProp.Type.IsGenericEnumerable())
{
var t = c.SourceProp.Type.GetGenericArguments()[0];
if(t.IsSimple())
return c.SourceProp.Value;
var tlist = typeof(List<>).MakeGenericType(t);
dynamic list = Activator.CreateInstance(tlist);
var addMethod = tlist.GetMethod("Add");
foreach(var sourceItem in (IEnumerable)c.SourceProp.Value)
{
addMethod.Invoke(list, new[] { RetrieveComplexSourceValue(sourceItem) });
}
return list;
}
//Get a source value that is in the right state and is tracked if needed.
var itemStateToInject = RetrieveComplexSourceValue(c.SourceProp.Value);
return itemStateToInject;
}
private object RetrieveComplexSourceValue(object source)
{
//If the source is a non-tracked type, or the source is a new value, then return its value.
if(!source.ImplementsIEntity(_entityTypesAndKeysCache) || source.IsEntityIdNull(_entityTypesAndKeysCache))
return source;
object sourceItemFromContext;
//Handle recursive entities, this could probably be cleaned up.
if(source.ImplementsIRecursiveEntity())
{
var itemKey = source.GetEntityIdString(_entityTypesAndKeysCache) + " " + ObjectContext.GetObjectType(source.GetType());
//If we have a context item for this key already, just return it. This solves a recursion problem with self-linking items.
if(_potentialParentObjectDump.ContainsKey(itemKey))
return _potentialParentObjectDump[itemKey];
//Get the source from the context to ensure it is tracked.
sourceItemFromContext = GetSourceItemFromContext(source);
//Add the class into the object dump in order to avoid any infinite recursion issues with self-linked objects
_potentialParentObjectDump.Add(itemKey, sourceItemFromContext);
}
else
//Get the source from the context to ensure it is tracked.
sourceItemFromContext = GetSourceItemFromContext(source);
//Recursively use this injection class instance to inject the source state on to the context source state.
var itemStateToInject = sourceItemFromContext.InjectFrom(this, source);
return itemStateToInject;
}
private object GetSourceItemFromContext(object source)
{
if(source == null)
return null;
//Using dynamic here to "AutoCast" to an IEntity<>. We should have one, but it's important to note just in case.
dynamic sourceEntityValue = source;
var sourceEntityType = ObjectContext.GetObjectType(source.GetType());
var sourceKeyType = sourceEntityType.GetEntityKeyType();
var method = typeof(DomainMergeInjection).GetMethod("GetFromContext", BindingFlags.Instance | BindingFlags.NonPublic);
var generic = method.MakeGenericMethod(sourceEntityType, sourceKeyType);
var sourceItemFromContext = generic.Invoke(this, new object[] { new object[] { sourceEntityValue.Id } });
return sourceItemFromContext;
}
// ReSharper disable UnusedMember.Local
private TItem GetFromContext<TItem, TKey>(object[] keys) where TItem : class, IEntity<TKey>
// ReSharper restore UnusedMember.Local
{
var foundItem = _repository.GetDbSet<TItem>().Find(keys);
return foundItem;
}
}
public static class EntityTypeExtensions
{
/// <summary>
/// Determines if an object instance implements IEntity.
/// </summary>
/// <param name="entity"></param>
/// <param name="entityCache">A cache to hold types that do implement IEntity. If the cache does not have the Type and the Type does implement IEntity, it will add the type to the cache along with the </param>
/// <returns></returns>
public static bool ImplementsIEntity(this object entity, Cache<Type, Type> entityCache = null)
{
//We need to handle getting the proxy type if this is an EF Code-First proxy.
//Please see for more info: http://msdn.microsoft.com/en-us/library/dd456853.aspx
var entityType = ObjectContext.GetObjectType(entity.GetType());
if(entityCache != null && entityCache.Has(entityType))
return true;
var implementationOfIEntity = entityType.GetInterfaces().FirstOrDefault(x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof (IEntity<>));
if(implementationOfIEntity == null)
return false;
if(entityCache != null)
{
var keyType = implementationOfIEntity.GetGenericArguments()[0];
entityCache.Fill(entityType, keyType);
}
return true;
}
/// <summary>
/// Determines if an object instances implements IRecurisveEntity
/// </summary>
/// <param name="entity"></param>
/// <returns></returns>
public static bool ImplementsIRecursiveEntity(this object entity)
{
//We need to handle getting the proxy type if this is an EF Code-First proxy.
//Please see for more info: http://msdn.microsoft.com/en-us/library/dd456853.aspx
var entityType = ObjectContext.GetObjectType(entity.GetType());
var implementsIRecursiveEntity = entityType.GetInterfaces().Any(x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof(IRecursiveEntity<>));
return implementsIRecursiveEntity;
}
/// <summary>
/// Determines whether or not an Entity's Id is null. Will throw an exception if a type that does not implement IEntity is passed through.
/// </summary>
/// <param name="entity"></param>
/// <param name="entityCache"></param>
/// <returns></returns>
public static bool IsEntityIdNull(this object entity, Cache<Type, Type> entityCache = null)
{
bool isEntityIdNull = ExecuteEntityIdMethod<bool>("IsEntityIdNull", entity, entityCache);
return isEntityIdNull;
}
/// <summary>
/// Determines whether or not an Entity's Id is null. Will throw an exception if a type that does not implement IEntity is passed through.
/// </summary>
/// <param name="entity"></param>
/// <param name="entityCache"></param>
/// <returns></returns>
public static string GetEntityIdString(this object entity, Cache<Type, Type> entityCache = null)
{
string entityIdString = ExecuteEntityIdMethod<string>("GetEntityIdString", entity, entityCache);
return entityIdString;
}
private static T ExecuteEntityIdMethod<T>(string methodName, object entityInstance, Cache<Type, Type> entityCache = null)
{
if(!entityInstance.ImplementsIEntity(entityCache))
throw new ArgumentException(string.Format("Parameter entity of type {0} does not implement IEntity<>, and so ist not executable for {1}!", entityInstance.GetType(), methodName));
//We need to handle getting the proxy type if this is an EF Code-First proxy.
//Please see for more info: http://msdn.microsoft.com/en-us/library/dd456853.aspx
var entityType = ObjectContext.GetObjectType(entityInstance.GetType());
var keyType = entityCache != null ? entityCache[entityType] : entityType.GetEntityKeyType();
var method = typeof(EntityTypeExtensions).GetMethod(methodName, BindingFlags.Static | BindingFlags.NonPublic);
var generic = method.MakeGenericMethod(keyType);
T returnValue = (T)generic.Invoke(null, new[] { entityInstance });
return returnValue;
}
private static string GetEntityIdString<TKey>(IEntity<TKey> entity)
{
var entityIdString = entity.Id.ToString();
return entityIdString;
}
private static bool IsEntityIdNull<TKey>(IEntity<TKey> entity)
{
//We need to handle getting the proxy type if this is an EF Code-First proxy.
//Please see for more info: http://msdn.microsoft.com/en-us/library/dd456853.aspx
var entityType = ObjectContext.GetObjectType(entity.GetType());
if(entityType.IsPrimitive)
return false;
//NOTE: We know that this entity's type is NOT primitive, therefore we can cleanly test for null, and return properly.
// ReSharper disable CompareNonConstrainedGenericWithNull
var entityIdIsNull = entity.Id == null;
// ReSharper restore CompareNonConstrainedGenericWithNull
return entityIdIsNull;
}
public static Type GetEntityKeyType(this Type typeImplementingIEntity)
{
var implementationOfIEntity = typeImplementingIEntity.GetInterfaces().FirstOrDefault(x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof(IEntity<>));
if(implementationOfIEntity == null)
throw new ArgumentException(string.Format("Type {0} does not implement IEntity<>", typeImplementingIEntity));
var keyType = implementationOfIEntity.GetGenericArguments()[0];
return keyType;
}
}
public interface IEntity<TKey>
{
TKey Id { get; set; }
}
public interface IRecursiveEntity<TKey> : IEntity<TKey>
{
IRecursiveEntity<TKey> Parent { get; }
IEnumerable<IRecursiveEntity<TKey>> Children { get; }
}
you could use the detached object only as a DTO,
and after refill the object from context with values from the DTO
with ValueInjecter this would be:
//manually
conObj.InjectFrom(dto);
conObj.RefTypeProp.InjectFrom(dto.RefTypeProp);
...
//or by writing a custom injection:
conObj.InjectFrom<ApplyChangesInjection>(dto);
here's the Injection that will do that automatically, (I did it by modifying a bit the DeepClone Injection from VI's home page)
the trick here is that the Injection uses itself in the SetValue method
public class ApplyChangesInjection : ConventionInjection
{
protected override bool Match(ConventionInfo c)
{
return c.SourceProp.Name == c.TargetProp.Name;
}
protected override object SetValue(ConventionInfo c)
{
if (c.SourceProp.Value == null) return null;
//for value types and string just return the value as is
if (c.SourceProp.Type.IsValueType || c.SourceProp.Type == typeof(string))
return c.SourceProp.Value;
//handle arrays - not impl
//handle IEnumerable<> also ICollection<> IList<> List<> - not impl
//for simple object types apply the inject using the corresponding source
return c.TargetProp.Value
.InjectFrom<ApplyChangesInjection>(c.SourceProp.Value);
}
}
//Note: I'm not handling collections in this injection, I just wanted you to understand the principle,
you can look at the original http://valueinjecter.codeplex.com/wikipage?title=Deep%20Cloning&referringTitle=Home
I've got this function
public static AdoEntity.Inspector GetInspectorWithInclude(int id, List<string> properties)
{
using (var context = new Inspection09Entities())
{
var query = context.Inspector;
if (properties != null)
{
foreach (var prop in properties)
{
if (!string.IsNullOrEmpty(prop))
query.Include(prop);
}
}
return query.Where(i => i.ID == id).First();
}
}
which i use to get my "Inspectors" from the DB and an additional feature to specify what to "Include" with the data. So it takes a List<'string'> and includes them with the query. This function doesn't seem to work because the returned object still does not include the requested data. Could someone tell me what is wrong with this method/approach.
Thanks in advance.
Solution
Thank you to Misha N. suggestion, I have hatched this EF helper which extends the ObjectQuery class. Hopefully others may find it useful.
/// <summary>
/// The include extesion that takes a list and returns a object query with the included data.
/// </summary>
/// <param name="objectQuery">
/// The object query.
/// </param>
/// <param name="includes">
/// The list of strings to include.
/// </param>
/// <typeparam name="T">
/// </typeparam>
/// <returns>
/// An object query of T type with the included data.
/// </returns>
public static ObjectQuery<T> Include<T>(this ObjectQuery<T> objectQuery, List<string> includes)
{
ObjectQuery<T> query = objectQuery;
if (includes != null) includes.ForEach(s => { if (!string.IsNullOrEmpty(s)) query = query.Include(s); });
return query;
}
Usage example.
using(var context = new MyEntity())
{
var includes = new List<string>
{
"Address",
"Orders",
"Invoices"
}
return context.CustomerSet.Include(includes).First(c => c.ID == customerID);
}
Nothing is wrong with your approach, just one little thing need to be changed:
public static AdoEntity.Inspector GetInspectorWithInclude(int id, List<string> properties)
{
using (var context = new Inspection09Entities())
{
var query = context.Inspector;
if (properties != null)
{
foreach (var prop in properties)
{
if (!string.IsNullOrEmpty(prop))
query = query.Include(prop);// <--- HERE
}
}
return query.Where(i => i.ID == id).First();
}
}
ObjectQuery.Include() method is returning altered ObjectQuery object, you haven't been doing changes to the inital query.
Hope this helps