Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 8 years ago.
Improve this question
I'm trying to figure out how to set a property as a primary key. What I mean is, I have a POCO object I'm trying to define a key for like this:
public class POCO
{
[PrimaryKey]
int Id;
string Name;
int Age;
}
Then I'm trying to access it like this:
public static object ReturnKeyValue(this POCO poco)
{
return poco.[PrimaryKey]; //should return Id
}
What am I doing wrong here?
I had something similar, which to which I found a solution yesterday, so I'm happy to share it.
Something you need to know is that what you're trying to do will never work when you have a composite key, which means, having a POCO object that has a primary key that conists out of more than 1 single element.
Let's say that I have the following class (POCO):
public class Person : EntityBase<int>
{
#region Properties
/// <summary>
/// Gets or sets the id of the entity.
/// </summary>
[Key]
public TKey Id { get; set; }
/// <summary>
/// Gets or sets the name.
/// </summary>
public string Name { get; set; }
/// <summary>
/// Gets or sets the firstname.
/// </summary>
public string FirstName { get; set; }
/// <summary>
/// Gets or sets the <see cref="Manager"/>.
/// </summary>
public Manager Manager { get; set; }
#endregion
}
I'm using Entity Framework here, therefore the attribute that defines the Primary Key is called Key and not PrimaryKey as in your example.
Now, I do have class that acts a repository. That class hold all the objects of Person in an object, in my Test-scenario, it's holding those objects in an HashSet:
private readonly HashSet<TEntity> _entitiesCollection = new HashSet<TEntity>();
Where TEntity is offcourse the Person entity.
Further, this class does have a List<PropertyInfo>' object, named_keyProperties`, that will hold all the keys for the object.
Now, I do have a method that will find all the properties that act as a key for the given object:
private void GetKeyProperties()
{
_keyProperties = new List<PropertyInfo>();
var properties = typeof(TEntity).GetProperties();
foreach (var property in from property in properties from attribute in property.GetCustomAttributes(true).OfType<KeyAttribute>() select property)
{ _keyProperties.Add(property); }
}
No, you can for example select all your that matches a given value for the primary keys. This can be achieved with a method like:
protected virtual TEntity Find(params object[] keyValues)
{
if (keyValues.Length != _keyProperties.Count) throw new ArgumentException("Incorrect number of keys passed to find method");
var keyQuery = this.AsQueryable();
keyQuery = keyValues.Select((t, i) => i).Aggregate(keyQuery, (current, x) => current.Where(entity => _keyProperties[x].GetValue(entity, null).Equals(keyValues[x])));
return keyQuery.SingleOrDefault();
}
Or, for example, if you want to perform an update of an entity, you can execute the following:
public void Update(TEntity entity)
{
// First the original entity is retrieve by searching the key, this item is then removed from the collection
// Then a new item is being added to the collection.
var original = Find(_keyProperties.Select(e => e.GetValue(entity)).ToArray());
Detach(original);
_entitiesCollection.Add(entity);
}
What this does is searching the original entity based on the primary key, remove that entity and then add the updated one again.
So, I hope this helps.
Can the POCO class's PrimaryKey property be made public, with a getter and setter, like this?
public class POCO
{
[PrimaryKey]
public int Id { get; set; }
string Name;
int Age;
}
If so, then the following extension method should return the PrimaryKey field's value for any given POCO instance.
public static object ReturnKeyValue(this POCO poco)
{
return (from p in poco.GetType().GetProperties()
let attr = p.GetCustomAttributes(typeof(PrimaryKeyAttribute), true)
where attr.Length == 1
select p).First().GetValue(poco, null);
}
Related
I want to get a list of fields with an Attribute Sync.Field on each of the field in the class. The field can / cannot have the attribute of Sync.Field
I have been trying the following, but having trouble getting the custom attribute for each field.
FieldInfo[] fiClass = typClass.GetFields();
FieldInfo[] lst = fiClass
.Where(c => c.CustomAttribute().GetType() == typeOf(Sync.Field))
.ToList();
I have a generic collection class, which uses a data class to match an SNMP table with data class fields. Like JsonProperty matches deserialised values to properties. In the same way I define a SNMPPropertyAttribute. The attribute itself is
[AttributeUsage(AttributeTargets.Property, Inherited = false, AllowMultiple = false)]
sealed class SNMPPropertyAttribute : Attribute
{
public SNMPPropertyAttribute(string propertyOID) => PropertyOID = new ObjectIdentifier(propertyOID);
public ObjectIdentifier PropertyOID { get; }
}
When in the table constructor, I'm making a dictionary of data fiels and their OIDs from the attribute:
public SNMPTableEntity()
{
snmpPoperties = new Dictionary<ObjectIdentifier, PropertyInfo>();
foreach (PropertyInfo myProperty in GetType().GetProperties(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public))
{
CustomAttributeData snmpAttribure = myProperty.CustomAttributes.Where(x => x.AttributeType == typeof(SNMPPropertyAttribute)).FirstOrDefault();
if (snmpAttribure != null)
snmpPoperties.Add(new ObjectIdentifier((string)snmpAttribure.ConstructorArguments[0].Value), myProperty);
}
}
It looks similar to what are you trying to acheive, so hopefully it helps. But the difference, is that I'm using properties, not fields. Not sure if it makes a big difference, but...
There is an example of using:
public class InterfaceTableEntity : SNMPTableEntity
{
/// <summary>
/// A unique value for each interface. Its value ranges between 1 and the value of ifNumber. The value for each interface must remain constant at least from one re-initialization of the entity's network management system to the next re- initialization.
/// </summary>
[SNMPProperty("1.3.6.1.2.1.2.2.1.1")]
protected Integer32 ifIndex { get; set; }
/// <summary>
/// A textual string containing information about the interface. This string should include the name of the manufacturer, the product name and the version of the hardware interface.
/// </summary>
[SNMPProperty("1.3.6.1.2.1.2.2.1.2")]
protected OctetString ifDescr { get; set; }
/// <summary>
/// The type of interface, distinguished according to the physical/link protocol(s) immediately `below' the network layer in the protocol stack.
/// </summary>
[SNMPProperty("1.3.6.1.2.1.2.2.1.3")]
protected Integer32 ifType { get; set; }
}
If you have the FieldInfo, you can get an instance of its attribute using this code:
var attr = fieldInfo.GetCustomAttributes().OfType<Sync.FieldAttribute>().SingleOrDefault();
See my example on DotNetFiddle.
How can we call a function that is defined abstract in a generic base class.
I have a generic
class Class1<T> where T : class, new()
and multiple classes which derive from it like
Class2: Class1<Class2>
Class3: Class1<Class3>
The generic class has 3 functions
1-> accept a dynamic object and puts all the values to corresponding properties in the object of derive
2-> accepts the ID, looks for the corresponding row in database pass the dynamic object to func1 and return the result
3-> a listall function which returns all rows in table
Here is the generic code
public abstract partial class Class1<T> where T : class, new()
{
public static EntityLayout EntityLayout { get; protected set; }
[TypeAttributes(TypeAttributes.Options.IsPrimary, TypeAttributes.Options.IsAutoIncrement)]
/// <summary> Automatically Incremented 64 bit Integer Primary Key
/// represents the Unique ID of each row in Table </summary>
public long ID { get; set; }
/// <summary> Converts the row returned from Database to Object </summary>
/// <param name="row"></param>
/// <returns></returns>
public abstract T GetDetails(dynamic row);
public static T GetDetails(long ID)
{
var row = Shared.SessionWrapper.Current.globaldbcon.QuerySingle("SELECT * FROM ["
+ EntityLayout.ContainerName + "].["
+ EntityLayout.TableName + "] WHERE ID=#0", ID);
if (row != null) return GetDetails(row);
return new T();
}
public static List<T> ListAll()
{
List<T> result = new List<T>();
foreach (var row in Shared.SessionWrapper.Current.globaldbcon.Query("SELECT * FROM ["
+ EntityLayout.ContainerName + "].["
+ EntityLayout.TableName + "]")) result.Add(GetDetails(row));
return result;
}
}
An example class Implementation
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using Arinsys.Database;
namespace WebApplication1.Models
{
[EntityAttributes(EntityAttributes.Options.TestingEnabled)]
public class Class3 : Class1<Class3>
{
static Class3()
{
EntityLayout.DisplayName = "Users";
}
/// <summary> User ID of the User </summary>
public long UID { get; set; }
/// <summary> User ID of the User if defined in Universal Data Store </summary>
public long UDSID { get; set; }
/// <summary> Login ID of User </summary>
public string LoginID { get; set; }
/// <summary> Registered email of the user. If not set will be set same as LoginID </summary>
public string Registeredemail { get; set; }
[TypeAttributes(TypeAttributes.Options.IsPassword)]
/// <summary> Password of user </summary>
public string Password { get; set; }
/// <summary> A Unique Security Stamp used for activation/deactivation of account or similar intense tasks </summary>
public string SecurityStamp { get; set; }
/// <summary> Timezone ID of the Default Timezone of User </summary>
public string DefaultTimezone { get; set; }
/// <summary> Current Status of User </summary>
public string CurrentStatus { get; set; }
/// <summary> Discriminator which defines the type of user in multi-user heirarchy scenario </summary>
public string UserType { get; set; }
/// <summary> Number of failed login attempts in total or same session depending upon configuration. Resets after Successful Login </summary>
public short FailedAttempts { get; set; }
/// <summary> Date Time of Last Failed Login Attempt in UTC </summary>
public DateTime LastFailedAttempt { get; set; }
/// <summary> Date Time of Last Successful Login in UTC </summary>
public DateTime LastLogin { get; set; }
/// <summary> Creation Date of User Account in UTC </summary>
public DateTime CreationDate { get; set; }
public override Class3 GetDetails(dynamic row)
{
Class3 result = new Class3();
if (row != null)
{
result.ID = Convert.ToInt64(row.ID);
result.UID = Convert.ToInt64(row.UID);
result.UDSID = Convert.ToInt64(row.UDSID);
result.UserType = row.UserType;
result.LoginID = row.LoginID;
result.Password = row.Password;
result.Registeredemail = row.Registeredemail;
result.SecurityStamp = row.SecurityStamp;
result.DefaultTimezone = row.DefaultTimezone;
result.CurrentStatus = row.CurrentStatus;
result.FailedAttempts = Convert.ToInt16(row.FailedAttempts);
result.LastFailedAttempt = Convert.ToDateTime(row.LastFailedAttempt);
result.LastLogin = Convert.ToDateTime(row.LastLogin);
result.CreationDate = Convert.ToDateTime(row.CreationDate);
}
return result;
}
}
}
Its been two weeks searching for the answer everywhere before posting, but couldn't find the solution.
All i want is that ListAll function should call 1st function. Since it's defined abstract i am sure the deriving class has to have an implementation (even though it might be just throw NotImplementException, but implementation is guaranteed)
I first defined the implementation of 1st function in generic class itself through reflection. Though that works, but its very slow, did performance bench-marking by starting/stopping a Stopwatch at start/end of controller action and it took approx 35 seconds for just 100 rows, so it's surely not something for production use.
Points to note
Static cannot be defined abstract
Cannot access instance member from static context
Cant use reflection because of performance issues
Possible Solutions i guess are closest ( but i am unable to understand how to use them in my case)
convert all methods to instance methods and use singleton
using interfaces
define a static method in derived class and assume it will be there in all classes, if i go this way then how to access static method on T in that case
What i want to achieve is that ListAll function should call 1st function accepting a dynamic object.
Some questions which come very close are these, but none of them solves my query.
Stack Overflow Q1 Stack Overflow Q2 Stack Overflow Q3
Looks like the design should be like this
public abstract partial class Class1<T> where T : Class1<T>, new()
{
protected abstract void Load(dynamic row);
private static T GetItem(dynamic row)
{
var item = new T();
if (row != null)
item.Load(row);
return item;
}
public static T GetDetails(long ID)
{
var row = Shared.SessionWrapper.Current.globaldbcon.QuerySingle("SELECT * FROM ["
+ EntityLayout.ContainerName + "].["
+ EntityLayout.TableName + "] WHERE ID=#0", ID);
return GetItem(row);
}
public static List<T> ListAll()
{
List<T> result = new List<T>();
foreach (var row in Shared.SessionWrapper.Current.globaldbcon.Query("SELECT * FROM ["
+ EntityLayout.ContainerName + "].["
+ EntityLayout.TableName + "]")) result.Add(GetItem(row));
return result;
}
}
and the sample implementation
public class Class3 : Class1<Class3> {
{
// ...
protected override void Load(dynamic row)
{
// No need to check for null, it is enforced by the base class
ID = Convert.ToInt64(row.ID);
UID = Convert.ToInt64(row.UID);
// ...
}
}
Basically you explore the Curiously recurring template pattern supported by .NET generic class constraints (T : Class1<T>) to ensure the derived class contains the abstract Load method, while the new T() part is enforced by the new() constraint.
Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 3 years ago.
Improve this question
If I have a class which represent a mapping to a specific table in my db in somehow.
This class contains about 30 properties.
I have created the CRUD Methods.
And find myself need to another (UPDATE) method which should update just two fields.
What should I do in a good manner with simple example?
Using my exist method, Filling the whole object and update all the fields including my intended two fields? (Useless work)
Create static Method with another name (but I want to keep my method name because it's expressive)! And takes two parameters?
I would go by by creating two separate interface and create overloaded functions for each interface. I would group properties based on usage, like I want status to be updated some time separate from other common properties.
public interface ICommonProperties
{
public string P1{get; set;}
public string P2{get; set;}
public string P3{ get; set; }
}
public interface ITrackable
{
public string Status{get; set;}
}
public class FinalClass : ICommonProperties, ITrackable
{
public string P1{get; set;}
public string P2{get; set;}
public string P3{get; set;}
public string Status{get; set;}
}
public class FinalClassOperations
{
public void Update(FinalClass finalClassInstance) { }; //Updates everything
public void Update(ICommonProperties finalClassInstance) { }; //Updates only ICommonProperties
public void Update(ITrackable finalClassInstance) { }; //updates only Status.
}
Additionally, if you want you can create a separate class for just updating the status, and that would still fit in:
public class Tracker : ITrackable{
public string Status{get; set;}
}
But yes, if the two properties cannot be separated out logically, I would not do that and keep them together.
I would suggest to follow your second option but there is no need to change the name as the number of method parameter will be different on both it's
Let's as walk into few example
I will try to create an similar situation, I hope it's your situation. you can clarify if i got wrongly the question.
CLASSES AND METHOD
/// <summary>
/// CLass to store properties related to database
/// </summary>
class ObjectoA
{
public string A{get; set;}
public string B{get; set;}
public string C{ get; set; }
}
/// <summary>
/// class to call method to update.
///
/// </summary>
class ObjectB
{
/// <summary>
/// update method.
/// I would go with this solution.
/// optionlay you can call the method which receive parameter of object
/// </summary>
/// <param name="A"> Object with properties mapped to database</param>
/// <param name="updatetwoproperties">Optional paramneter to decide which update to run.
/// the default value should be for update that run most. For your need if you want to create an update methods for other
/// two sets of parameter a suggest you create an Enum and pass this enum as optional parameter instead of bool parameter or you
/// can pass as string and map each string value to specific update inside. IF YOU NEED EXAMPLE
/// REPLAY ON COMMENTS</param>
/// <returns></returns>
public bool update(ObjectoA A, bool updatetwoproperties=false)
{
//method implementation
if (updatetwoproperties)
{
//implement a update to all field
}
else
{
//implement update just to two field
}
return true;
}
/// <summary>
/// update method based on parameter to update
/// </summary>
/// <param name="a">this properties is mapped on database</param>
/// <param name="b">this propertie is mapped on database</param>
/// <returns></returns>
public bool update(string a, string b)
{
//method implementation e validate the return value
return true;
}
}
/// <summary>
/// I don't suggest to use this solution because
/// it will add a method on string type while this method isn't related to string
/// I just added here as a workaround for you.
/// </summary>
public static class ObjectC
{
public static bool update(this string a, string b)
{
//implementation of update and validate the return value
return true;
}
}
CALLING METHOD AND EXPLANATION
static void Main(string[] args)
{
ObjectB B = new ObjectB(); //Class with methods
ObjectoA A = new ObjectoA(); //object with properties
#region Using Optional parameter to decide which update to run
//Calling a method to update all columns
B.update(A);
//Calling a method to update two columns
B.update(A, true);
#endregion
#region Using polymorphism to update
//Calling a method to update all columns
B.update(A);
//Update only using paramenter
B.update(A.B, A.C);
#endregion
//NOT RECOMMEND BECAUSE THIS UPDATE ISN'T RELATED TO STRING TYPE
#region Using extension method to update
//Calling a method to update all columns
B.update(A);
//using the extension method on variable type
A.B.update(A.C);
#endregion
//WE COULD USE EXTENSION METHOD ON YOUR OBJECT BUT IT WILL FAIL BECAUSE WE ALREADY AS UPDATE METHOD ON CLASS
//IF YOU WANT TO SEE HOW JUST REPLAY
}
I SUGGEST YOU ADD OPTIONAL PARAMETER ON YOUR METHOD TO DECIDE WHICH UPDATE TO USE
It depends on what your priorities are on the project:
using your already existing update method is gonna update everything all the time, incressing traffic, IO and process time (validation and so on...)
If you're on a project where properties are timestamped, they would be updated even if the value hasn't really changed...
If you don't mind about all this, use your update() method all the time.
My personnal POV is: create a new method (with an explicit name). This will same process time from now on and thinking time in 2 years when you'll have to change this class ;)
I don't know if this is what you should do necessarily, but here's something you could do: Create a SetAll or SetMany or whatever method where you pass in another instance of your class (source). Check each property and if it's non-null, you set the destination object's property value to the source object's property value. Note that this tactic will depend on nullable types, and assumes you can ignore null values passed into a new setter method. Here's an illustration:
using System;
namespace BlogPartialUpdateTrick
{
public class SomeClass
{
public string FirstName { get; set; }
public string LastName { get; set; }
public int? HeightInches { get; set; }
public DateTime? Dob { get; set; }
public void SetAll(SomeClass source)
{
this.FirstName = source.FirstName ?? this.FirstName;
this.LastName = source.LastName ?? this.LastName;
this.HeightInches = source.HeightInches ?? this.HeightInches;
this.Dob = source.Dob ?? this.Dob;
}
public override string ToString()
{
return String.Format("fn: {0}, ln: {1}, height: {2}, DOB: {3}", FirstName ?? String.Empty, LastName ?? String.Empty,
HeightInches.HasValue ? HeightInches.Value.ToString() : "null", Dob.HasValue ? Dob.Value.ToShortDateString() : "null" );
}
}
}
In this first code sample, We have my spiffy class SomeClass. It's got 4 properties, all of which are nullable. The noteworthy part of this class is the SetAllMethod where I can pass in a source object which is also of type SomeClass. It sets this instance's property values to the values passed in the source parameter, but only if they're non-null. Here's a 2nd code blurb where I'm using this stuff:
using System;
using System.Windows.Forms;
namespace BlogPartialUpdateTrick
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
var destination = new SomeClass() { FirstName = "Freddy", LastName = "Fingers", Dob = DateTime.Parse("01/01/1970"), HeightInches = 72 };
var source = new SomeClass() { FirstName = null, LastName="Flippers", Dob = null, HeightInches = 80 };
destination.SetAll(source);
MessageBox.Show(destination.ToString());
}
}
}
Create a destination object, a source object, call the new method, voila! output is this:
"fn: Freddy, ln: Flippers, height: 80, DOB: 1/1/1970"
You should probably use Entity Framework and let the context do it for you. Using EF, you'll be able to update Entities like this :
try
{
var original = ObjectContext.Set<Request>().SingleOrDefault(x => x.Id.Equals(_request.Id));
if (original != null)
{
ObjectContext.Entry(original).CurrentValues.SetValues(_request);
}
return ObjectContext.SaveChanges();
}
catch (Exception ee)
{
return -1;
}
Want to create this table on application start if it does not exist.
Code:
public class Database : ApplicationEventHandler
{
protected override void ApplicationStarted(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext)
{
var db = applicationContext.DatabaseContext.Database;
//Cant add this table due to the ENUM
if (!db.TableExist("FormData"))
{
db.CreateTable<FormData>(false);
}
}
}
Model:
[PrimaryKey("Id")]
public class FormData
{
[PrimaryKeyColumn(AutoIncrement = true, IdentitySeed = 1)]
public int Id { get; set; }
[NullSetting(NullSetting = NullSettings.NotNull)]
public FormType Type { get; set; }
[NullSetting(NullSetting = NullSettings.NotNull)]
public string Data { get; set; }
[NullSetting(NullSetting = NullSettings.NotNull)]
public DateTime Date { get; set; }
}
Error message:
[InvalidOperationException: Sequence contains no matching element]
System.Linq.Enumerable.First(IEnumerable1 source, Func2 predicate) +415
Umbraco.Core.Persistence.SqlSyntax.SqlSyntaxProviderBase1.FormatType(ColumnDefinition column) +1225
Umbraco.Core.Persistence.SqlSyntax.SqlSyntaxProviderBase1.Format(ColumnDefinition column) +155
Umbraco.Core.Persistence.SqlSyntax.SqlSyntaxProviderBase1.Format(IEnumerable1 columns) +144
Umbraco.Core.Persistence.SqlSyntax.SqlSyntaxProviderBase`1.Format(TableDefinition table) +131
Umbraco.Core.Persistence.PetaPocoExtensions.CreateTable(Database db, Boolean overwrite, Type modelType) +161
Umbraco.Core.Persistence.PetaPocoExtensions.CreateTable(Database db, Boolean overwrite) +121
Looking at the error I dont think there is a solution to this without updating the core but here is to hoping you guys can help
Working on the answer that #Ryios gave, I think that something like this is good:
/// <summary>
/// Don't use this to get or set.
/// This must however be kept as public or db.CreateTable()
/// will not insert this field into the database.
/// </summary>
[NullSetting(NullSetting = NullSettings.NotNull)]
[Column("type")]
public int _type { get; set; }
/// <summary>
/// This field is ignored by db.CreateTable().
/// </summary>
[Ignore]
public FormType Type
{
get
{
return (FormType)_type;
}
set
{
_type = (int)value;
}
}
In the code, Type should be used rather than _type so that the enum can be benefitted from. _type is only present as the field that is inserted into the database table.
Without modifying PetaPoco or using a different fork, you can use a solution like this,
[Column(type), NullSetting(NullSetting = NullSettings.NotNull)]
public int TypeAsInt { get; set; }
[Ignore]
public FormType TypeAsEnum { get { return (FormType)TypeAsInt; } }
Peta poco will Ignore properties tagged with Ignore, meaning it won't try to use them when creating a table, or selecting a result. Instead TypeAsInt will get created in the table as type of type int.
Then in your code you can use TypeAsEnum any time you want the FormType version which is cast from TypeAsInt.
I believe the issue is because your class has a non-sql type on it, so it doesn't know what to do with it. In particular you have a property called "FormType" which is of type "FormType". PetaPoco has no idea what that is in terms of SQL Column types. If you want to auto-create the table, you'll need to make sure your class uses only property types that can be mapped to SQL Column types.
You may also run into a couple of issues, for example if you want a column to be nvarchar(max) there's no way to tell the persistence layer to do max, so you have to set it to nvarchar(x) where x is a number, and then run an alter statement to change the type to nvarchar(max) after you've created the table.
I have a dictionary like this:
/// <summary>
/// Gets the leave entitlement details.
/// </summary>
/// <value>The leave entitlement details.</value>
public Dictionary<string, EmployeeLeaveEntitlement> LeaveEntitlementDetails { get; set; }
And I want to map it to the database. Is it possible to use a protected or private List<> for that? such as:
/// <summary>
/// Gets the leave entitlement details.
/// </summary>
/// <value>The leave entitlement details.</value>
public Dictionary<string, EmployeeLeaveEntitlement> LeaveEntitlementDetails { get; set; }
public List<EmployeeLeaveEntitlement> LeveEntitlementStore
{
get
{
List<EmployeeLeaveEntitlement> leaveEntitlements = new List<EmployeeLeaveEntitlement>();
foreach (KeyValuePair<string, EmployeeLeaveEntitlement> leaveType in LeaveEntitlementDetails)
{
leaveEntitlements.Add(leaveType.Value);
}
return leaveEntitlements;
}
set
{
foreach (EmployeeLeaveEntitlement item in value)
{
this.LeaveEntitlementDetails.Add(item.LeaveType, item);
}
}
}
Can anyone help me?
Entity Framework does not presently support mapping a Dictionary natively.
See the following for more information and work-arounds:
Entity Framework 4 POCO with Dictionary
EF Code First - Map Dictionary or custom type as an nvarchar
http://social.msdn.microsoft.com/Forums/en-US/adonetefx/thread/a51ba903-2b8b-448e-8677-d140a0b43e89/
EF Core 2.1 introduced a new feature called value conversion:
Value converters allow property values to be converted when reading from or writing to the database.
This feature highly simplifies the serialization approach mentioned in previous answers, which means, the introduction of on an additional "helper" property and the marking of your dictionary property as [NotMapped] becomes unnecessary.
Here are some lines of code tailored to your case (note, I am using Json.NET, but feel free to use your serializer of choice):
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
namespace My.Name.Space
{
public class MyEntity
{
public int Id { get; set; }
public Dictionary<string, EmployeeLeaveEntitlement> LeaveEntitlementDetails { get; set; }
}
public class MyEntityConfiguration : IEntityTypeConfiguration<MyEntity>
{
public void Configure(EntityTypeBuilder<MyEntity> builder)
{
builder.ToTable("MyEntity");
builder.HasKey(e => e.Id);
builder
.Property(e => e.LeaveEntitlementDetails)
.IsRequired()
.HasConversion(
v => JsonConvert.SerializeObject(v),
v => v == null
? new Dictionary<string, EmployeeLeaveEntitlement>() // fallback
: JsonConvert.DeserializeObject<Dictionary<string, EmployeeLeaveEntitlement>>(v)
);
}
}
}
Using a XML Column in DB
So today I came across the same problem, and after thinking about it I found a cool solution which I would like to share with the community even if I am late.
Basically I've made a wrapping system which saves the data in the Dictionary to the Database as XML Column, so later I can also query the XML from the DB if I want.
Pro of this approach
Easy to use
Fast implementation
You can use the dictionary
You can query the XML column
First of all here's the bone of all my models:
public abstract class BaseEntity
{
/// <summary>
/// ID of the model
/// </summary>
public int ID { get; set; }
}
Suppose I have a model which contain a Dictionary<string,string> and a String property which contains the logic to Serialize and Deserialize the dictionary in XML, like the following snippet:
public class MyCoolModel : Base.BaseEntity
{
/// <summary>
/// Contains XML data of the attributes
/// </summary>
public string AttributesData
{
get
{
var xElem = new XElement(
"items",
Attributes.Select(x => new XElement("item", new XAttribute("key", x.Key), new XAttribute("value", x.Value)))
);
return xElem.ToString();
}
set
{
var xElem = XElement.Parse(value);
var dict = xElem.Descendants("item")
.ToDictionary(
x => (string)x.Attribute("key"),
x => (string)x.Attribute("value"));
Attributes = dict;
}
}
//Some other stuff
/// <summary>
/// Some cool description
/// </summary>
[NotMapped]
public Dictionary<string, string> Attributes { get; set; }
}
Then I've implemented a BaseMapping class which ineherits from EntityTypeConfiguration<T>
class BaseMapping<TEntity> : EntityTypeConfiguration<TEntity>
where TEntity : Model.Base.BaseEntity
{
public BaseMapping()
{
//Some basic mapping logic which I want to implement to all my models
}
}
And after a Custom Mapping for MyCoolModel
class MyCoolModelMapping
: BaseMapping<Model.MyCoolModel>
{
public MyCoolModelMapping()
{
Property(r => r.AttributesData).HasColumnType("xml");
}
}
Now notice that when AttributesData value is requested by EntityFramework it just serialize the dictionary and the same happens when I retrive data from the DB and EntityFramework sets the data to the field, which then
deserializes the object and sets it to the dict.
And finally I have override the OnModelCreating of my DbContext
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Configurations.Add(new Mappings.BaseMapping<SomeOtherModel>());
modelBuilder.Configurations.Add(new Mappings.MyCoolModelMapping());
//Other logic
}
And that's it! Now I can use the dictionary from my business logic and this "wrapping" handles all the stuff need to save the data to the DB and retrive the data from it.
I had a similar problem with EF were I wanted to convert a query returned list, into a class property's dictionary equivalent. Very similar to how you want to have LeaveEntitlementDetails wrapped by LeveEntitlementStore
For example:
class A
{
[NotMapped()]
public Dictionary<int, DataType> Data {get; set}
//refers to Data.Values
public ICollection<DataType> DataAsList {get; set}
}
Where I wanted DataAsList to essentially wrap Data.Values
After a lot of trial and error, I discovered that EF, for collections (maybe more) alters through the getter's returned value (rather than the setter).
I.e. when initializing from my db:
var pollquery=From bb In DBM.Dbi.DataTable.Includes("DataAsList")
Where bb.Id = id
Select bb;
ClassA objInstance = pollquery.First();
ClassA.DataAsList's setter was never being called, but the getter was during EF's internal construction of my object.... Conclusion: EF is using a reference retrieved from the getter of property ClassA.DataAsList, and adding objects to it.
So I wrapped my getter's return value for DataAsList in an ObservableCollection and added a handler for CollectionChanged args and sure enough, my handler for CollectionChanged was picking up .Add calls.
So heres my hackaround-workaround:
class A : INotifyPropertyChanged
{
//So we can let EF know a complex property has changed
public event PropertyChangedEventHandler INotifyPropertyChanged.PropertyChanged;
//here's our actual data, rather than an auto property, we use an explicit member definition so we can call PropertyChanged when Data is changed
private Dictionary<int, DataType> m_data = new Dictionary<int, DataType>();
//not mapped property as it's not mapped to a column in EF DB
[NotMapped()]
public Dictionary<int, DataType> Data {
get { return m_data; }
set {
m_data = value;
//now call PropertyChanged for our Front (so EF will know it's been changed)
if (PropertyChanged != null) {
PropertyChanged(this, new PropertyChangedEventArgs("DataAsList"));
}
}
}
//this is our front for the data, that we use in EF to map data to
[DebuggerHidden()]
public ICollection<DataType> DataAsList {
get {
ObservableCollection<DataType> ob = new ObservableCollection<DataType>(Data.Values());
ob.CollectionChanged += Handles_entryListChanged;
return ob;
}
set {
//clear any existing data, as EF is trying to set the collections value
Data.Clear();
//this is how, in my circumstance, i converted my object into the dictionary from an internal obj.Id property'
foreach (DataType entry in value) {
entryions.Add(entry.id, entry);
}
}
}
//This will now catch wind of any changes EF tries to make to our DataAsList property
public void Handles_entryListChanged(object sender, NotifyCollectionChangedEventArgs e)
{
//Debugger.Break()
switch (e.Action) {
case NotifyCollectionChangedAction.Add:
foreach (DataType entry in e.NewItems) {
m_data.Add(entry.Id, entry);
}
break;
default:
Debugger.Break();
break;
}
}
}
Note the Magic is the:
public ICollection<DataType> DataAsList {
get {
ObservableCollection<DataType> ob = new ObservableCollection<DataType>(Data.Values());
ob.CollectionChanged += Handles_entryListChanged;
return ob;
}
where we subscribe to any changes made to the returned list and Handles_entryListChanged where we handle and essentially replicate any changes made.
As mentioned in here, One important thing after object serialization, is that when updating the entity and changing items in the dictionary, the EF change tracking does not pick up on the fact that the dictionary was updated, so you will need to explicitly call the Update method on the DbSet<> to set the entity to modify in the change tracker.
there is also another good sample here