Hello,
I wonder if it's possible to add mapping class like DapperExtensions or Nhibernate has?
Or i could start doing that but i do want to know if the feature will be merge.
IMO i don't like attributes on data objects (entities or dto). so by creating ClassMap<T> it allows us to decoupled the entity from the ORM.
Please advice
Hi @guy-lud ,
Thank you for this request. Of course that is very possible. I am now considering this already, and might be available on the next minor version (1.11.0).
This is related to the existing request (#399).
FYI: Please consider sharing the desired syntax you'd like so I can consider it during the implementation.
@sellig - can you join the discussion here?
@guy-lud, if you have the idea on how to do this, then please do share your class (I will make sure to cover it with the Unit/Integration Tests).
Actually, there is already TypeMapper class where the name is also meant for the purpose. But the implementation is for different mapping purpose (between the .NET CLR Type and DbType). If I am to introduce a new one, I would like to covert both Class and Property mappings (and the name ClassMap<T> invalidates the purpose). Thinking about ClassMapper is only for class mappings and PropertyMapper is only for the properties (ClassProperty, Linq Expression, Field, etc). What do you think?
Hi Michael
Have you already implement something ?
@sellig - yes, but not yet released. The property mapper is still on going as I am stucked at Linq Expression. Can you be able to test if I am going to release a beta for this?
Yes of course, on my current project, I can test the alternative for
[Map("[schema].[TabelName]")]
[Primary]
[Identity]
attribute
Do you implement a Fluent Interface to do it as suggested in #399 ?
That is the intention in the end. For now, what I had implemented are these classes.
TBH, some of them are still under development.
They were implemented as static class mappers. To use it, you have to call each one by one for now.
The fluent mapper like you mentioned will be implemented on top of these classes. I hope that clear up the things.
A fluent interface to replace some _Query_ methods will be also more user readable & userfriendly,
Delete.Table("[schema].[tabelname]")
.Where(new { VehicleId = vehicle.Id, Category = AssociatedEntitiesCategories.PublicSupport })
.WithTransaction( _sqlConnectionFactory.Transaction)
Query.Table("[schema].[tabelname]")
.Top(10)
.Fields(Field.From("Id", "Name"))
.Where(searchFields)
.Orderby(OrderField.Parse(new { Name = Order.Ascending }))
instead of ( abit awfull with all the parameters that can be null)
.Delete( tableName:"[fund].[Vehicle_AssociatedEntities]",
new { VehicleId = vehicle.Id, Category = AssociatedEntitiesCategories.PublicSupport },
null,
null,
transaction: _sqlConnectionFactory.Transaction
Query(tableName:"[schema].[tabelname]",
where: searchFields,
fields: Field.From("Id", "Name"),
OrderField.Parse(new { Name = Order.Ascending }),
top:10)
Hi @mikependon,
Thank you for responding so quickly, i really appreciate it.
Long long time ago (when i had fewer children) i forked DapperExtensions and completely changed it to suite my needs.
Maybe the ClassMap<T> code might help?
@guy-lud - great, will look at it as well. Thanks
@guy-lud - I am trying to implement all the supporting classes towards your ClassMap<T> or the fluent class @sellig mentioned.
It would be great if you can make a PR of this towards RepoDb, since you own the code-line.
Can you wait til I completed the necessary underlying classes?
So the codes inside of this line,
var result = PropertyMapBuilder.BuildMap(expression);
will be something like this.
void BuildMap(Expression expression)
{
// Call the ClassMapper if the class mapping is needed
ClassMapper.Add<T>("[dbo].[TableName]");
// Call the PropertyMapper if the property mapping is needed
PropertyMapper.Add<T>(e => e.Property, "[ColumnName]");
// Call the TypeMapper if the type mapping is needed
TypeMapper.Add<T>(e => e.BirthDate, DbType.DateTime2);
...
}
The reason to this, I would like control everything in the underlying classes using the cachers and mappers for performance and efficiency purposes (and also), the compiler is a bit attached to both (cachers and mappers).
Are you okay with this? Thanks
@sellig - thanks, as always. Let us handle your concerns about operations fluency (Delete, Insert, Update, Merge) in a separate thread/story.
Btw, RepoDb has choosen the path of non-IQueryable (but a Dapper) like for the purpose of simplicity and direct. But I already discussed with somebody about introducing the support to IQueryable and Joins. So this feature may be resulted to an extended package like RepoDb.Queryable (or RepoDb.Joinable for joins). Are you okay to file a story for this in a separate issue?
Hi Michael,
Your proposing implementation is fine for me, it will be a great addition.
Thank you
H @mikependon,
It would be great. can you ping me when your changers are ready?
@guy-lud - Thanks! I will let you know soon. :)
@sellig - I am now done with my changes. The documentation will be followed. I need your help to actually help me test this as an actual user. Can you help me test the solutions?
MapAttribute (Property Level)
To add a property mapping, use the PropertyMapper.Add() class/method. I suggest to use the PropertyMappedNameCache.Get() class/method (not the underlying implementation at PropertyMapper.Get()) to get the mappings.
MapAttribute (Class Level)
To add a class mapping, use the ClassMapper.Add() class/method. I suggest to use the ClassMappedNameCache.Get() class/method (not the underlying implementation at ClassMapper.Get()) to get the mappings.
PrimaryAttribute
To add a primary mapping, use the PrimaryMapper.Add() class/method. I suggest to use the PrimaryCache.Get() class/method (not the underlying implementation at PrimaryMapper.Get()) to get the mappings.
IdentityAttribute
To add an identity mapping, use the IdentityMapper.Add() class/method. I suggest to use the IdentityCache.Get() class/method (not the underlying implementation at IdentityMapper.Get()) to get the mappings.
PropertyHandlerAttribute (in-progress)
To add an identity mapping, use the PropertyTypeHandlerMapper.Add() class/method. I suggest to use the PropertyTypeHandlerCache.Get() class/method (not the underlying implementation at PropertyTypeHandlerMapper.Get()) to get the mappings.
TypeMapAttribute
To add a database type mapping, use the TypeMapper.Add(Property, Column) class/method. I suggest to use the TypeMapCache.Get() class/method (not the underlying implementation at TypeMapper.Get()) to get the mappings.
Class Level Mapping
Still use the same method implemented at TypeMapper.Add<T>().
Please be aware of the difference between
TypeMapper.Add<T>()(TypeLevel) andTypeMapper.Add<T>(Property)(PropertyLevel).
The thorough Unit Tests has been written here.
Please do not hesitate to revert if you have questions.
@guy-lud - you can now start building the ClassMap<T> fluent class that you would like. Can you se the classes above I mentioned to establish the mappings? I made an update to the Core implementation of the library to maximize the usage of the Cache classes (which also uses the Mapper classes).
Also, the mapping supports the following.
PropertyMapper.Add<T>("PropertyName", "ColumnName");)PropertyMapper.Add<T>(new Field("PropertyName", "ColumnName"));)PropertyMapper.Add<T>(e => e.PropertyName, "ColumnName");)Such approach supports to Property Mapping, Type Mapping. The Identity Mapping, Primary Mapping and Class Mapping only supports (Expression-Based and Type-Based).
I am not saying you should support all, but it would be nice to have it supported. Otherwise, I suggest prioritizing the expression way.
Please revert if you have further questions. Cheers and thanks! Looking forward on you PR.
Please use this release RepoDb v1.11.0-alpha1.
> Install-Package RepoDb -version v1.11.0-alpha1
The documentation will be followed in the actual site. Btw, do not forget to visit the blogs, I had placed this activity as an actual blog for RepoDb.
Hi Gents, I have made an optimizations and added the usage of PropertyHandlerCache into the compiler.
Please use the RepoDb (v1.11.0-beta1) version on your development and testing.
> Install-Package RepoDb -version 1.11.0-beta1
Please do not hesitate to revert if you have questions. Thanks!
Hi Michael,
I have tested ClassMapper, all is fine but I have an issue with IdentityMapper and PrimaryMapper
public class Currency
{
//[Primary]
//[Identity]
public int Id { get; set; }
public string Code { get; set; }
public string Name { get; set; }
}
and trying to set Primary/Identity configuration as
SqlServerBootstrap.Initialize();
ClassMapper.Add<Currency>("[common].[currencies]");
IdentityMapper.Add<Currency>(c => c.Id);
PrimaryMapper.Add<Currency>(v => v.Id);
and get this exception
RepoDb.Exceptions.InvalidExpressionException: 'Expression 'c => Convert(c.Id, Object)' is not valid.'
Your code is very simple and is covered by the Unit Test here. In which mapper you encountered the problem?
Can you also share your class? Thanks
I'm using the class Currency I have put in my comment. No more.
I get the error at Execution on line IdentityMapper.Add
Upon looking at your class and codes, it seems to be very simple and should be working. I am away from PC for now, so I will check this one later.
Btw, you can use the IdentityMapper.Add<Currency>("Id") and PrimaryMapper.Add<Currency>("Id") temporarily so you can proceed on your testing. It is still the same as using the Linq Expression. I would assume the issue would be in the Linq Expression (parser).
@sellig - it was my missed on the Linq Parser. I only parsing the 'Members' and not the 'Binary' and 'Unary' expressions (which make more sense if you wrote and integer-based property and some boolean-based like PrimaryMapper.Add<Class>(e => e.Property == 1). That kind of thing is handled even though it is not the proper way.
In addition to this, I added a much more extensive actual Integration Tests for this Implicit Mapping feature. It is complete a complete replica of this Integration Tests for Object Mapping.
Please use the RepoDb v1.11.0-beta2.
> Install-Package RepoDb -version 1.11.0-beta2
And again, please do not hesitate to revert if you have questions.
Thank you.
Using the beta2, it's near resolved.
I have another issue, I think you must improve the cache key when entity derives from a base class
public abstract class Entity
{
public long Id { get; protected set; }
//...
}
public class DomainA: Entity
{
//...
}
public class DomainB: Entity
{
//...
}
Doing
IdentityMapper.Add<DomainA>(c => c.Id);
IdentityMapper.Add<DomainB>(c => c.Id);
raise this error
The identity property mapping to type 'RepodbTest.Entity' already exists.
Oh, great! Thanks for this @sellig , I will guard this scenario. Just revert if you found any further problems.
@sellig - I made a fix on your issue with regards to Interface Property collision. Would you like to have a new beta build? After that, I may be waiting for @guy-lud update when it comes to Fluent Class Mapper. Please do let me know if you need a new build.
Thank you Michael.
Yes, I will appreciate a new build.
Best
Gilles
Hi, a new beta release has been pushed (RepoDb v1.11.0-beta3). Please do not hesitate to revert if you still have problems. Thanks
Thank you. It's working well now.
HI @mikependon,
Thanks for making the changes. I will try to do so ASAP.
@guy-lud - no hurry, just make your PR and I will handle the Unit and Integration Tests for that. Btw, please disallow yourself to use the following.
Add, Get and Remove of PropertyMapper class with PropertyInfo and ClassProperty argument.Get of PropertyMapNameCache class with PropertyInfo and ClassProperty argument.I am planning to remove them as exposing the direct PropertyInfo and ClassProperty invalidates the architectural design. Simply use the PropertyMapper.Add<TEntity>(PropertyName|Field|Expression) instead (same with Get and Remove methods).
@guy-lud - hey mate, do you have any updates on this?
Hey @mikependon,
I've started the work but unfortunately i don't have a lot of oss time this month.
I hope to finished the work by the end of the month.
@guy-lud - thank you. No worries, will make sure to include your changes to our next release. Just leave a message updates here once that's ready.
HI @mikependon, can you allow my user to push branches please?
@mikependon,
I've added a simple class map since you did all the work with your mappers :)
Things that I've implemented in my mapper (DapperExceptions) that i could find here (maybe it exists yet i lack the knowledge) is Ignore, read only and id self generated (not returning a value). One more thing please, I could not find in the docs support for projection. does it exists?
can you allow my user to push branches please?
@guy-lud - are you failing? TBH, never I had tried that before. Would you be of guidance where to enable this? Or, is this something related to collaborator?
One more thing, by using the mappers the expression
Never thought that up-stream command is failing until you become a collaborator. Never tried that before :) - thanks for this.
One more thing please, I could not find in the docs support for projection.
RepoDb understand the schema of the DB and by comparing it to the target .NET CLR type (for pull/push operations), RepoDb automatically removes the unnecessary columns from the operations. Thus make it work even you have extra (or less) property (in all operations). I will call it auto project. However, the map projection (manual projection) that you are asking is not supported.
Things that I've implemented in my mapper (DapperExceptions) that i could find here (maybe it exists yet i lack the knowledge) is Ignore, read only and id self generated (not returning a value).
Again, RepoDb understand your schema, it ignores the unnecessary columns (push/pull) operations. The readonly is not supported. The id self-generated is via Identity attribute, but it is not necessary at all since RepoDb automatically return back the id (even without explicitly or implicitly assigning a mapping).
Hi @guy-lud , I am creating a Unit Tests and Integration Tests for ClassMap<T>.
private class ClassMapperTestClass
{
public int PrimaryId { get; set; }
public string PropertyString { get; set; }
}
private class ClassMapperTestClassMap :
ClassMap<ClassMapperTestClass>
{
public ClassMapperTestClassMap()
{
Table("[dbo].[Table]");
Primary(e => e.PrimaryId);
Identity(e => e.PrimaryId);
Property(e => e.PropertyString)
.Column("ColumnString")
.DbType(DbType.DateTime2);
}
}
As a caller, I need to instantiate an object ClassMapperTestClassMap to setup the mapping (see the code snippets below).
var map = new ClassMapperTestClassMap(); // Mapping is enabled automatically
It seems that the mapping trigger is invalid as we put it in the constructor? IMHO
Is it possible to put an abstract method in the base class ClassMap<T> (ie: Setup(), Register() or Initialize()) so the user is pushed to call this method after the instantiation?
var map = new ClassMapperTestClassMap();
map.Initialize(); // or map.Setup() or map.Register() // Mapping is enabled here
Your thoughts?
Hi @mikependon,
If the mapping is in the constructor all you need is to "new" up a mapper class. Thus if you decide to scan assemblies for classMap you can use Actvator.NewInstance(classMapType) and no other work is needed.
Yeah, I got your point. That's exactly what I commented above. My experience of using the class map is seems like a static class as I do not need to call any method on the instance to enable the mapping. You can see it here. It just enabled it by default during the new keyword, and it is now a question whether we are okay with that or not.
IMHO, we should always not do the job in the constructor, but instead do it somewhere like classMapInstance.Initialize() or via static class ClassMapRepository.Scan() as you mentioned, etc.
I totally agree about c'tor should not do any work. But in case of frameworks where someone else is in-charge of the 'new' -ing it's OK since it's managed. (IMO).
Another option is to create an abstract method named public abstract void DoSomething() and the developer will do the work yet again it's the same thing because the framework is incharge of the process. You will place a try catch in the right places. know the limitation and reduce risks :).
Again thank you for letting me be a part of this :)
You nailed down both of my concerns :). Thanks! It is up to you whether you go to your ClassMapRepository or public abstract void Setup() (or some name), but I also prefer your map repository that will scan all the ClassMaps in the Assembly. If it is okay, would you be able to implement that?
One drawback to that is, how can it see private ClassMaps? Like what I am doing at the Unit Tests and Integration Tests?
On the other hand, I will do the XML comments and finalize the test suites for you. (I had placed the initial test suites on your branch class-mapper and already merged that to the main-branch).
Hi @mikependon, I would be able to continue the work over the weekend if that fine by you?
Yes, you are correct with item 1. Anytime is fine by me, just do it in your most convenient time.
For item 2, I can take a look on the XML comments and Unit Test.
Btw, I can see that we can make an additional overload of each method to cater the override argument. This argument can also be passed on the underlying calls to the mappers. I think I can manage this thing during the XML updates.
@guy-lud - I have made the following updates.
By the way, I realized that we need to rename specific interfaces and properties. Can I ask you a favor, can you rename the following.
IIdentityOptions to IIdentityMappingOptionsIPrimaryOptions to IPrimaryMappingOptionsIPropertyOptions to IPropertyMappingOptionsIdentityOptions to IdentityMappingOptionsPrimaryOptions to PrimaryMappingOptionsPropertyOptions to PropertyMappingOptionsThe reason of rename is to add the class a more context based on its purpose. Also, I really would like you to do it so the history will not remove as you are the creator of these.
Once you're finish with the rename, can I ask you to offer a PR again together with your propose repository?
I am planning to make a release with this feature this week. Please advise and thanks!
@guy-lud - I have to finalize this one within the week to move on to others. Would you be able to give estimate here? Or, would you share me your plans on this. Thanks
@guy-lud - I had decided not to include your PR on the latest version. Alternatively, this request of yours can be addressed by FluentMapper class.
FluentMapper
.Entity<Customer>() // Which Entity Model?
.Table("[sales].[Customer]") // Table/View Mapping
.Primary(e => e.Id) // Primary Mapping
.Identity(e => e.Id) // Identity Mapping
.Column(e => e.Id, "CustomerId") // Column Mapping
.Column(e => e.Name, "CustomerName") // Column Mapping
.DbType(e => e.DateOfBirth, DbType.DateTime2) // DbType Mapping
.PropertyHandler<CustomerAddressPropertyHandler>(e => e.Address); // PropertyHandler Mapping
The feature is available at RepoDb (v1.11.0).
> Install-Package RepoDb -version 1.11.0
Please visit the Implicit Mapping to learn more.
Hi @mikependon ,
Sorry for not being responsive. I had to leave for personals reasons.
Can you please update me on what is needed? is the PR will be merged?
@guy-lud - it is ok. I've wait for you for 2 weeks and it leaves me no choice but to cut the PR and move forward so the organization I worked could move as well. Your PRs were merged but was not part of a release. Instead, I created the FluentMapper class as a response. Many things happened after that, and I also issued some fixes specifically when mapping the Property Handlers.
I am hoping that you would still consider contributing to this OSS in the future.
@mikependon , again, sorry for make you waiting. If you need something please do be shy.
I love OSS so don't worry about contributing :)
Maybe we could discuss the next version in the future and not make everything static :).
Most helpful comment
Hi @mikependon,
Thank you for responding so quickly, i really appreciate it.
Long long time ago (when i had fewer children) i forked DapperExtensions and completely changed it to suite my needs.
Maybe the
ClassMap<T>code might help?