I have a employee object as shown below
class emp
{
public int EmpID { get; set; }
public string EmpName { get; set; }
public int deptID { get; set; }
}
I need to create a mapping either in this class or a different class to map the properties with column name of my SQL
for eg. EmpdID="employeeID"
EmpName="EmployeeName"
deptID="DepartmentID"
When from my asp.net page when I create the employee class and pass it to a function:
for eg: emp e=new emp();
e.EmpID=1;
e.EmpName="tommy";
e.deptID=10;
When the emp object is populated and passed to the buildValues function it should return array of ComumnName(e.g.employeeID):Value(e.g.1),EmployeeName:tommy,DepartmentID:10)
string[] values=buildValues(emp);
public string[] buildValues(emp e)
{
string[] values=null;
return values;
}
I have 2 questions:
1. Where do I specify the mappings
2. How do I use the mappings in my buildValues function shown above and build the values string array.
I would really appreciate if you can help me with this
You need to use Reflection.
Specifically, you need to loop over typeof(Employee).GetProperties().
This is a solved problem. Do some research on ORM's and have a look at this SO question: .Net ORM that works well with MySQL
First of all, (as it was already said) it's best to leave this kind of mappings to an ORM tool, and just forget about them. However, ORM tools tend to be too "maternal" in protecting you from the gory details of data access and such, so it can be complicated to extend them, or change their behaviour.
That said, you could create a special class (Mappings) that would hold all mapping code. The mappings themselves are best kept in a dictionary, something like this:
static class Mappings
{
private static Dictionary<Type, Dictionary<string, string>> TypeMappings;
private static Dictionary<string, string> EmployeeMapping;
//... other mapped classes
static Mappings()
{
TypeMappings = new Dictionary<Type, Dictionary<string, string>>();
EmployeeMapping = new Dictionary<string, string>();
EmployeeMapping.Add("EmpID", "EmployeeID");
EmployeeMapping.Add("EmpName", "EmployeeName");
EmployeeMapping.Add("DeptID", "DepartmentID");
TypeMappings.Add(typeof(Employee),EmployeeMapping);
//... other mapped classes
}
public static string[] BuildValues<T>(T item)
{
if (!TypeMappings.ContainsKey(typeof(T)))
throw new Exception("wrong call");
Dictionary<string, string> mapping = TypeMappings[typeof(T)];
List<string> results = new List<string>();
foreach (var keyValuePair in mapping)
{
string propName = keyValuePair.Key;
string dbName = keyValuePair.Value;
PropertyInfo pi = typeof(T).GetProperty(propName);
object propValue = pi.GetValue(item, null);
results.Add(string.Format("{0}:{1}", dbName, propValue));
}
return results.ToArray();
}
}
Here, the TypeMappings is a dictionary of all mapped classes, whose mappings in turn are in propertyName - databaseName dictionaries.
The BuildValues method, takes those names, reflects the values, and build a results string.
Related
Is there a way to fill a dictionary property with Entity Framework Core?
For performance reasons, we like to search in the application instead of the database. As a list won’t scale well, we like to use a dictionary.
For example (simplified example)
class Course
{
public Dictionary<string, Person> Persons { get; set; }
public int Id { get; set; }
}
class Person
{
public string Firstname { get; set; }
public string Lastname { get; set; }
}
Things I tried
Naively just add a dictionary property. This will result the in following error:
System.InvalidOperationException: The property 'Persons' could not be mapped, because it is of type 'Dictionary' which is not a supported primitive type or a valid entity type. Either explicitly map this property, or ignore it using the '[NotMapped]' attribute or by using 'EntityTypeBuilder.Ignore' in 'OnModelCreating'.
Try adding a value conversion (with HasConversion), but conversion one only works on a single item and not on collections. The HasMany already gives a compile error:
builder
.HasMany<Person>(c => c.Persons) //won't compile, Persons isn't a IEnumerable<Person>
.WithOne().HasForeignKey("PersonId");
Creating a custom collection class (inherited from Collection<T> and implement InsertItem, SetItem etc.) – unfortunately this also won’t work because EF Core will add the item to the collection and first after that will fill the properties (at least with our OwnsOne properties, that is not in the demo case) - SetItem won't be called afterwards.
Adding a "computed" property that will build the dictionary, the setter won't be called (the list is updated every time with partly values, a bit the same as above). See try:
class Course
{
private Dictionary<string, Person> _personsDict;
public List<Person> Persons
{
get => _personsDict.Values.ToList();
set => _personsDict = value.ToDictionary(p => p.Firstname, p => p); //never called
}
public int Id { get; set; }
}
Of course I could build a dictionary in the Repository (using the Repository pattern), but that’s tricky as I could forget some parts – and I really prefer compile time errors over run-time errors and declarative style over imperative style code.
Update, to be clear
this isn't a code first approach
the idea to change the mapping in EF Core, so no database changes. - I haven't tagged the database on purpose ;)
If I use a List instead of Dictionary, the mapping works
It's a 1:n or n:m relationship in the database (see HasMany - WithOne)
I don't think saving a dictionary is a good idea (I can't even image how it would be done in the database). As I can see from you source code you are using the FirstName as key. In my opinion you should change the dictionary to a HashSet. This way you can keep the speed but also save it to the database.
Here is an example:
class Course
{
public Course() {
this.People = new HashSet<Person>();
}
public ISet<Person> People { get; set; }
public int Id { get; set; }
}
After this you can create a dictionary from it, or keep using the hashset. Sample for dictionary:
private Dictionary<string, Person> peopleDictionary = null;
public Dictionary<string, Person> PeopleDictionary {
get {
if (this.peopleDictionary == null) {
this.peopleDictionary = this.People.ToDictionary(_ => _.FirstName, _ => _);
}
return this.peopleDictionary;
}
}
Please note that this would mean that your People Set becomes unsynced after you add/remove to/from the dictionary. In order to have the changes in sync you should overwrite the SaveChanges method in your context, like this:
public override int SaveChanges() {
this.SyncPeople();
return base.SaveChanges();
}
public override int SaveChanges(bool acceptAllChangesOnSuccess) {
this.SyncPeople();
return base.SaveChanges(acceptAllChangesOnSuccess);
}
public override Task<int> SaveChangesAsync(CancellationToken cancellationToken = default) {
this.SyncPeople();
return base.SaveChangesAsync(cancellationToken);
}
public override Task<int> SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = default) {
this.SyncPeople();
return base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken);
}
private void SyncPeople() {
foreach(var entry in this.ChangeTracker.Entries().Where(_ = >_.State == EntityState.Added || _.State == EntityState.Modified)) {
if (entry.Entity is Course course) {
course.People = course.PeopleDictionary.Values.ToHashSet();
}
}
}
EDIT: In order to have a running code, you will need to tell the EF not to map the dictionary, via the NotMapped Attribute.
[NotMapped]
public Dictionary<string, Person> PeopleDictionary { ... }
Seems someone has been struggling with that and found solution. See: Store a Dictionary as a JSON string using EF Core 2.1
The definition of the entity is as follows:
public class PublishSource
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
[Required]
public string Name { get; set; }
[Required]
public Dictionary<string, string> Properties { get; set; } = new Dictionary<string, string>();
}
In the OnModelCreating method of the database context I just call HasConversion, which does the serialization and deserialization of the dictionary:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<PublishSource>()
.Property(b => b.Properties)
.HasConversion(
v => JsonConvert.SerializeObject(v),
v => JsonConvert.DeserializeObject<Dictionary<string, string>>(v));
}
One important thing I have noticed, however, 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 modified in the change tracker.
You could add a new property PersonsJson for storing the JSON data. It automatically serializes or deserializes the JSON data into the Persons property when data is retrieved from DB or stored to DB. Persons property is not mapped, only PersonsJson is mapped.
class Course
{
[NotMapped]
public Dictionary<string, Person> Persons { get; set; }
public string PersonsJson
{
get => JsonConvert.SerializeObject(Persons);
set => Persons = JsonConvert.DeserializeObject<Dictionary<string, Person>>(value);
}
public int Id { get; set; }
}
Create a partial class of the type generated by EF.
Create a wrapper class that holds a dictionary or implement IDictionary.
Implement the Add function so it also adds the value to the list that EF uses.
The first time a method that operates on the Persons list or dictionary is called make sure they are properly initialized
You would end up with something like:
private class PersonsDictionary
{
private delegate Person PersonAddedDelegate;
private event PersonAddedDelegate PersonAddedEvent; // there can be other events needed too, eg PersonDictionarySetEvent
private Dictionary<string, Person> dict = ...
...
public void Add(string key, Person value)
{
if(dict.ContainsKey(key))
{
// .. do custom logic, if updating/replacing make sure to either update the fields or remove/re-add the item so the one in the list has the current values
} else {
dict.Add(key, value);
PersonAddedEvent?.(value); // your partial class that holds this object can hook into this event to add it its list
}
}
// ... add other methods and logic
}
public partial class Person
{
[NotMapped]
private Dictionary<string, Person> _personsDict
[NotMapped]
public PersonsDictionary<string, Person> PersonsDict
{
get
{
if(_personsDict == null) _personsDict = Persons.ToDictionary(x => x.FirstName, x => x); // or call method that takes list of Persons
return _personsDict;
}
set
{
// delete all from Persons
// add all to Persons from dictionary
}
}
}
public List<Person> Persons; // possibly auto-generated by entity framework and located in another .cs file
if your going to access the list of Persons directly then you need to also modify your partial class so that adding to the list will add to the dictionary (perhaps using a wrapper for the Persons list or a wrapper class all together)
there are some improvements to be made if dealing with large data sets or needing optimization, eg not deleting/re-adding all elements when setting new dictionary
you might need to implement other events and custom logic depending on your requirements
i don't know if this would solve the problem or not, but when i tried to run your provided code. it triggered a runtime error that required me to modify the Persons property declaration to like like this
public Dictionary<string, Person> Persons { get; set; } = new Dictionary<string, Person>();
this eliminated the runtime error and every thing went fine.
I was trying to make a List with different types of objects (or another enumerable / indexed type). I didn't use to create own enumerator on classes, so I was googling more and more.
Now I am here with (probably) one of the worst idea
public List<dynamic> IndexedProperties
{
get
{
// some code
// returned list (I wrote only types)
return new List<dynamic> { String, String, String, DateTime, DateTime, String };
}
}
So I could use it like that:
foreach (var prop in data.IndexedProperties)
Console.WriteLine(prop);
So, to precise the question - is this a good way to do that? (I think that using dynamic is not good at all, only in critical situation)
Can I do it more safe?
If all you're doing with the members of the list is showing a string then you can use:
public List<object> IndexedProperties = new List<object>();
...
foreach(var prop in data.IndexedProperties)
Console.WriteLine(prop.ToString());
Alternatively, you could replace the dynamic type with an interface or abstract class that is augmented with a property that aggregates the object:
public class DynamicContainer
{
public dynamic ValueMember{ get; set; }
public item(dynamic valueMember)
{
this.ValueMember = valueMember;
}
}
Then in your list constructor would go something like:
public List<DynamicContainer> IndexedProperties
{
get
{
return new List<DynamicContainer>
{
new DynamicContainer(stringObject1),
new DynamicContainer(stringObject2),
new DynamicContainer(stringObject3),
new DynamicContainer(dateTimeObject1),
new DynamicContainer(dateTimeObject2),
new DynamicContainer(stringObject4)
};
}
}
...
foreach(var prop in data.IndexedProperties)
Console.WriteLine(prop.ValueMember.ToString());
I've a problem with updating a dictionary
I also have read many topics about this saying all the same (e.g. Link)
ExampleDict[key] = value;
I was trying this to my own code:
Model:
public Dictionary<int, string> Info { get; set; }
public Dictionary<int, string> GetInfo(int id)
{
Info = new Dictionary<int, string>();
var game = db.Games.Find(id);
var playerOne = db.Players.Find(game.PlayerOneId);
var playerTwo = db.Players.Find(game.PlayerTwoId);
var PlayerOneName = playerOne.Nickname;
var PlayerTwoName = playerTwo.Nickname;
int scoreOne = CountScorePlayerOne();
int scoreTwo = CountScorePlayerTwo();
// old aproach:
Info.Add(scoreOne, PlayerOneName);
Info.Add(scoreTwo, PlayerTwoName);
//new approach
Info[key] = scoreOne;
return Info;
}
Error
The name 'key' does not exist in the current context
Note: I'm a student and this is for a school project.
Any ideas on what I am doing wrong?
Solved through the code sample from Jon Senchyna, Thank you!
It appears that you are never declaring the variable key. You probably did not mean to use "key" in the "new approach" line.
In addition, I believe your Dictionary is backwards, as it looks like the player name should be the key, not their score. The way you are currently declaring it (Dictionary<int, string>), you are creating a dictionary whose keys are int (score?) and whose values are string (name?).
Here are my suggested edits:
// Corrected dictionary definitions
public Dictionary<string, int> Info { get; set; }
public Dictionary<string, int> GetInfo(int id)
{
// You may want to use a local variable here instead
Info = new Dictionary<string, int>();
...
Info[PlayerOneName] = scoreOne;
Info[PlayerTwoName] = scoreTwo;
return Info;
}
Here are some useful links for learning more about using the Dictionary class (and C# in general if you browse around a bit):
MSDN
DotNetPearls
In the example link in your question there's the method:
public static void SafeDictionaryAdd(Dictionary<string, object> dict, string key, object view)
Which has a string key as an argument. You need to define that key, too.
I've read few posts, and I'm still having troubles with adding properties to a class in runtime. It should be simple, because I have a class like this:
public class MyClass
{
String Template;
String Term;
}
During runtime, I have to add few attributes, like Phone, Email (it depends...).
Could someone please explain me how to add these properties during class initialization?
Srecko
I don't think adding a property is the right thing to do here.
The attributes like "Email" or "Phone" are just some additional pairs of a key and a value. You could use a Dictionary, but that would prevent you from using a key more than once (more than one email address for a contact for example). So you could just as well use a List<KeyValuePair<string, string>>. Like that:
public class MyClass
{
String Template;
String Term;
public List<KeyValuePair<string, string>> Attributes { get; private set; }
public MyClass() {
Attributes = new List<KeyValuePair<string, string>();
}
public void AddAttribute(string key, string value) {
Attributes.Add(new KeyValuePair<string, string>(key, value));
}
}
// to be used like this:
MyClass instance = new MyClass();
instance.AddAttribute("Email", "test#example.com");
instance.AddAttribute("Phone", "555-1234");
If you have c# 4.0 you can use the Expando object.
for earlier versions of c#, the generally accepted way of doing this is to create a "property bag" i.e. a collection (or dictionary) of key value pairs
dynamic foo = new ExpandoObject();
foo.Bar = "test";
you could add an dictionary with for your Key/Value-Pairs.
Then if you add your attributes you just add Key = Attributename, Value = YourValue to the dictionary.
Reading is as easy - just get the Value to the Key = Attributename from your dictionary.
I am in need of creating a dynamically extend-able class in C#.
The goal is to create a class what can contain all info from a given contact from an Android SQLite Contacts table. The table's structure is kinda weird, as it does not have set field names, but uses colums of 'field name' and 'field content'.
That's what I want to turn into a usable format where the code reads the database, and for each entry creates the matching sub-variable. Such I want to know the best method to do so (I guess a simple
{
this.(variableNames[i].ToString()) = variableContent[i];
}
will not do it), what is the least resource-eating, but fastest (and easiest) way.
And also if we are here, is there ANY method to call a type's (let's say, I create a new Contact with e-mail, workplace, name, and image tags, but these variables names' are unknown) ALL sub-variables (Contact.image, Contact.FirstName, Contact.Email, etc) dynamically?
Of course there will be standardized fields what should be in ALL contact (one of the three names, phone number, e-mail #work and #home, and such), but these should be called dynamically too.
Use a Dictionary<string,string> instead.
Dictionary<string,string> contactInfo = new Dictionary<string,string>();
public void ImportContact()
{
...
// for each fieldName and fieldValue from your table
contactInfo.Add(fieldName, fieldValue);
...
// check that all standard fields are present, if desired
}
public string FirstName
{
get { return contactInfo["FirstName"]; }
}
If you are willing to go with dynamic typing, you can use the dynamic type in C# 4. You can use ExpandoObject or DynamicObject as a base for your Contact types.
Here is an example of a Contact class that can work both statically typed with some pre-defined properties; and can have properties attached to it at run-time. When treating it statically, you can still get the values by using the indexer:
class Contact : DynamicObject
{
private readonly Dictionary<string, object> bag = new Dictionary<string, object>();
public string FirstName { get; set; }
public string LastName { get; set; }
public object this[string key]
{
get { return bag[key]; }
set { bag[key] = value; }
}
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
if (bag.ContainsKey(binder.Name))
{
result = bag[binder.Name];
return true;
}
return base.TryGetMember(binder, out result);
}
public override bool TrySetMember(SetMemberBinder binder, object value)
{
bag[binder.Name] = value;
return true;
}
}
Which you can then use like this:
// Contact is statically typed.
Contact c = new Contact();
c.FirstName = "test";
// Treat as dynamic and attach some extra properties:
dynamic dynContact = c;
dynContact.AddressOne = "Somewhere";
dynContact.AddressTwo = "Someplace else";
Console.WriteLine(dynContact.AddressOne);
Console.WriteLine(dynContact.AddressTwo);
Other than using dynamic, you cannot create a new class with dynamically typed properties. After all, how would you consume those properties ? You might be better off creating a class containing the properties that you must have; and put the rest in a Dictionary<string,object>.
If you're using .NET 4.0, there's dynamic support. You can create objects something like this:
var newContact = new object { FirstName = "name", LastName = "name", etc... };
Alternatively, you might want to try using a Dictionary.