Closed WellspringCS closed 4 years ago
I'm also confused regarding the relationship between FluentMapper.Initialize() and DommelMapper.
I find that I must use DommelMapper to get table names corrected automagically. But DommelMapper does not run any code for the column names, though I have it set up to do so.
Once again, I am sure a working demo would set me straight. I just cannot find one.
Which versions of Dommel and Dapper FluentMap are you using?
<PackageReference Include="Dapper" Version="2.0.35" />
<PackageReference Include="Dapper.FluentMap.Dommel" Version="1.7.0" />
I don't have any other packages specific to this topic. You're not going to tell me it's a missing include? Yikes. During the churn I at one point removed this:
<PackageReference Include="Dapper.FluentMap" Version="1.8.0" />
<PackageReference Include="Dommel" Version="1.11.0" />
If there's a magic combo, I'm on the edge of my seat to know it. :)
Hmm, the 1.x versions are supposed to work together. Could you try that?
I'm so desperate, I will try anything ... but while I'm trying, could you clarify? What includes are you suggesting? Something like...
<PackageReference Include="Dapper" Version="1.60.6" />
<PackageReference Include="Dapper.FluentMap.Dommel" Version="1.7.0" />
If it speaks to my desperation, I was reading the code in here just now: https://github.com/StackExchange/Dapper/pull/1349/files
...Wondering if a new version of Dapper is about to provide some of this functionality internally.
That seems fine. This does not however:
DommelMapper.SetColumnNameResolver(new ColumnConventionMapper());
DommelMapper.SetTableNameResolver(new CustomTableNameResolver());
What are those?
public class CustomTableNameResolver : DommelMapper.ITableNameResolver
{
public string ResolveTableName(Type type)
{
return Regex.Replace(input: type.Name,
pattern: "([A-Z])([A-Z][a-z])|([a-z0-9])([A-Z])", replacement: "$1$3_$2$4").ToLower() + "s";
}
}
public class ColumnConventionMapper : DommelMapper.IColumnNameResolver
{
public string ResolveColumnName(PropertyInfo propertyInfo)
{
return Regex.Replace(input: propertyInfo.Name,
pattern: "([A-Z])([A-Z][a-z])|([a-z0-9])([A-Z])", replacement: "$1$3_$2$4").ToLower();
}
}
or were you meaning why am I using them?
Sorry to be providing info by drip feed. is there a demo project anywhere that shows a working sample (even a tiny one) that from end-to-end handles Dommel's version of Contrib as well as fluent translation of entities to Postgres snake_case from standard C# classes?
Ok, so to share a minor victory and maybe shed light... This actually works:
<PackageReference Include="Dapper" Version="1.60.6" />
<PackageReference Include="Dapper.FluentMap.Dommel" Version="1.7.0" />
...
Dapper.DefaultTypeMap.MatchNamesWithUnderscores = true;
FluentMapper.Initialize(config =>
{
config.AddMap(new ClientMap());
config.AddMap(new VwClientMap());
config.ForDommel();
});
...
DommelMapper.SetTableNameResolver(new CustomTableNameResolver());
...
var x = (await _dapperContext.Connection().GetAllAsync<VwClient>()).ToList();
whereas if you comment out the SetTableNameResolver() call, the above code fails.
GraphQL.ExecutionError: Error trying to resolve clients
Did you take a look at the docs? You should either use
Dapper.DefaultTypeMap.MatchNamesWithUnderscores
Or
FluentMapper.Initialize
Don't use DommelMapper.SetTableNameResolver
directly. You should create a convention and add that to the Initialize method.
@henkmollema if I had been doing this for years or even months, I'd remember exactly where and how my problems arose, but my background in FluentMapper/Dommel/Contrib is measured in hours/days. Even where Dapper is concerned my depth has been shallow. So I cannot elaborate with clarity on all routes taken.
THAT SAID...
My experience with Dapper.DefaultTypeMap.MatchNamesWithUnderscores and understanding of it is that it works one-way. It's not going to solve my insert/update issues. (Note lament at end of this one)
FluentMapper.Initialize is what i worked with for some 10-15 hours over the past few days. No combination that I could find worked. It wasn't for lack of effort.
I'm very thankful for Dapper and your contributions to the community. I really did want to use Dommel/FluentMapper. However I simply couldn't figure out how to make it work. If there were a working example out there, I'd have mimicked it and saved 2 days. Sadly, I'm not able to see one.
If you happen to know of example code that uses Dapper/Dommel/FluentMapper so as to manage the back-and-forth between C# standard syntax (AwesomePascalCasing) and Postgres snake_in_the_grass (am I tipping my hand regarding my preferences?) I'd be all over it!
Right now I'm writing up a customized column-mapping solution for my project. I don't know what else to do.
There is a sample at https://github.com/henkmollema/Dapper-FluentMap#convention-based-mapping. If that doesn't work please let me know and I will try it tomorrow.
@henkmollema that was the code I was staring at all yesterday. Could very much be entirely my failure to see what I was doing wrong, but again, it wasn't for lack of reading. :)
BTW I am certain that I tried for many hours on variations that did not include Dapper.DefaultTypeMap.MatchNamesWithUnderscores -- it was because that wasn't going to work with Contrib nor Dommel for that matter that I attempted to use a clean combo of just Dommel/FluentMapper . Thus my comment on SO here...
It seems that conventions aren't working with Dommel. I'm releasing a fix in a few hours.
I'll definitely give it a whirl when it's ready. Thx in advance.
Dapper.FluentMap.Dommel
version 1.7.1 should fix it. This works for me:
class Program
{
static void Main(string[] args)
{
FluentMapper.Initialize(config =>
{
config.AddConvention<PropertyTransformConvention>()
.ForEntity<Product>();
config.ForDommel();
});
using var con = new MySqlConnection("...");
con.Execute("create table if not exists Products (id int not null auto_increment, product_name varchar(255), primary key (Id))");
var id = con.Insert(new Product
{
ProductName = "Foo"
});
var product = con.Get<Product>(id);
Console.WriteLine(product?.ProductName ?? $"Product {id} not found");
Console.ReadKey();
}
}
public class Product
{
public int Id { get; set; }
public string ProductName { get; set; }
}
public class PropertyTransformConvention : Convention
{
public PropertyTransformConvention()
{
Properties()
.Configure(c => c.Transform(s =>
Regex.Replace(input: s, pattern: "([A-Z])([A-Z][a-z])|([a-z0-9])([A-Z])", replacement: "$1$3_$2$4").ToLower()));
}
}
Cool! I'll take a look and let you know how I fared. I'm wrapping up my custom fallback option but will hit this today. Thx @henkmollema
@henkmollema I've had the chance to try out 1.7.1 now. Notes below.
I'm not sure what I might be doing improperly, but I can say that the route shown here was the one that worked for my code, whereas others simply did not. (Once again, I did try a number of flavors...)
Dapper.DefaultTypeMap.MatchNamesWithUnderscores = true;
FluentMapper.Initialize(config =>
{
config.AddConvention<PropertyTransformConvention>()
.ForEntity<Client>()
.ForEntity<VwClient>();
config.ForDommel();
});
DommelMapper.SetKeyPropertyResolver(new CustomKeyPropertyResolver());
DommelMapper.SetColumnNameResolver(new ColumnConventionMapper());
DommelMapper.SetTableNameResolver(new CustomTableNameResolver());
...
...
public class PropertyTransformConvention : Convention
{
public PropertyTransformConvention()
{
Properties()
.Configure(c => c.Transform(s => Regex.Replace(input: s,
pattern: "([A-Z])([A-Z][a-z])|([a-z0-9])([A-Z])", replacement: "$1$3_$2$4")));
}
}
public class CustomKeyPropertyResolver : DommelMapper.IKeyPropertyResolver
{
public PropertyInfo ResolveKeyProperty(Type type, out bool x)
{
x = false;
string xy = type.Name;
if (xy.Substring(0, 2) == "Vw") xy = xy[2..];
var z = type.GetProperties().Single(p => p.Name == $"{xy}Id");
return z;
}
public PropertyInfo ResolveKeyProperty(Type type)
{
return type.GetProperties().Single(p => p.Name == $"{type.Name}Id");
}
}
public class CustomTableNameResolver : DommelMapper.ITableNameResolver
{
public string ResolveTableName(Type type)
{
return Regex.Replace(input: type.Name,
pattern: "([A-Z])([A-Z][a-z])|([a-z0-9])([A-Z])", replacement: "$1$3_$2$4").ToLower() + "s";
}
}
public class ColumnConventionMapper : DommelMapper.IColumnNameResolver
{
public string ResolveColumnName(PropertyInfo propertyInfo)
{
return Regex.Replace(input: propertyInfo.Name,
pattern: "([A-Z])([A-Z][a-z])|([a-z0-9])([A-Z])", replacement: "$1$3_$2$4").ToLower();
}
}
FWIW my table was really a view, so the postgres name was public.vw_clients and this needed to be mapped to VwClients. And because the name was VwClients for the entity, further massaging was necessary to arrive at the proper key. I've forgotten, honestly, but as I write this, I am wondering if several issues would have been avoidable if I provided attributes for the class objects ([Key], etc.)
Short is, it did work. I was able to retrieve and update data as well. So your fix definitely made things possible that weren't before.
Thx!
I've been scratching my head for the past 6 hours. I cannot get the convention-based approach to work. My db is in Postgres so it's the standard "convert-to-snake-case" situation.
Clues along the way: CustomTableNameResolver() does run. A stop in the code is reached. ColumnConventionMapper code never runs. (Might only get run when I use SqlBuilder.
Any thoughts? My startup.cs code includes
as noted in above code comments, this works...
...but requires a manual setup that I'm trying to avoid.
I am sure the above looks too much like a mix-and-match but since nothing is working, I've been trying every combination I can think of.
I would love to be able to (instead of
config.AddMap(new ClientMap())
) use this:and then call ...
but per my comments, the operation does not map data from the receiving entity into my Client class. Is there anything obvious that I'm doing wrong? If not (or maybe regardless) is there anywhere a working demo project? I have hunted high and low for example code and am coming up empty handed.