How to change the schema at runtime in Dapper-Extensions? - dapper

I am using Dapper Extensions but I have multiple schema names in My DB.
I found the an answer in below link but it assumes that I have only one schema name and this is not my case.
Dapper Extensions Change Schema
What is the right way to change the schema name at the runtime?

Probably you won't use it anymore, but maybe there are more guys up there trying to figure it out.
I've been checking out how dapper works when running GetAll<> and Insert<>.
What it does:
var type = typeof(T);
var cacheType = typeof(List<T>);
if (!GetQueries.TryGetValue(cacheType.TypeHandle, out string sql))
{
GetSingleKey<T>(nameof(GetAll));
var name = GetTableName(type); <--- key thing
sql = "select * from " + name;
GetQueries[cacheType.TypeHandle] = sql;
}
So I've checked what GetTableName was doing:
private static string GetTableName(Type type)
{
if (TypeTableName.TryGetValue(type.TypeHandle, out string name)) return name;
if (TableNameMapper != null)
{
name = TableNameMapper(type); <-- key thing
}
else
{
//NOTE: This as dynamic trick should be able to handle both our own Table-attribute as well as the one in EntityFramework
var tableAttr = type
#if NETSTANDARD1_3
.GetTypeInfo()
#endif
.GetCustomAttributes(false).SingleOrDefault(attr => attr.GetType().Name == "TableAttribute") as dynamic;
if (tableAttr != null)
{
name = tableAttr.Name;
}
else
{
name = type.Name + "s";
if (type.IsInterface() && name.StartsWith("I"))
name = name.Substring(1);
}
}
TypeTableName[type.TypeHandle] = name;
return name;
}
Solution:
So I thought I can implement my own table name mapper with code like this:
SqlMapperExtensions.TableNameMapper = DapperMapper.TableNameMapper();
And
public static SqlMapperExtensions.TableNameMapperDelegate TableNameMapper()
{
return (type) =>
{
var has = Attribute.GetCustomAttribute(type, typeof(CastleTableAttribute));
if (has != null)
{
return $"{ConfigurationProvider.Schema}.{type.Name}";
}
else
{
return type.Name;
}
};
}
With that name mapper you just need to mark your table with that.
[CastleTable]
class CubeTimestamps
{
[ExplicitKey]
public int cube_id { get; set; }
public DateTime cube_timestamp { get; set; }
}
you can implement your name mapper to use [TableName] attribute also. Because my simple implementation hides this feature.
Enjoy :)

Related

Enum switch in Java

I have this Java class where I am writing the code for applying the overrides. I want to know if using ENUM is appropriate or if I need to use the switch case, how can I use it? Also, I have the for loop that I need to use as a common block of code for each override type. Apart from that, I do have few separate fields that I need to code for each override type.
public class EWFMService
{
private WorkbrainSystemAccessService wsa = new WorkbrainSystemAccessService();
private static final org.apache.log4j.Logger logger = org.apache.log4j.Logger.getLogger(EWFMService.class);
private final static String ovrCalcGrp = "ovrCalcGrp";
private DBConnection conn = null;
private int empId;
private Date ovrDate;
private String ovrTime;
private String ovrAction;
public List<EWFMServiceData> getProcessEWFMOverrides(String userName, String password, List<EWFMServiceInputData> inputData)
throws WSApplicationException{
logger.debug("EWFM Service");
wsa.logOn(userName, password);
List<EWFMServiceData> returnList = new ArrayList<EWFMServiceData> ();
logger.debug("userName = " + userName);
DBConnection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try{
conn = new DBConnection(ConnectionManager.getConnection());
for (int i = 0; i < inputData.size(); i++)
{
Here I want to retrieve the emp_id from the database, store the value in a variable and be able to use the variable in the rest of my program. How do I do it? To retrieve the emp_id, I am using the following query.
conn = new DBConnection(ConnectionManager.getConnection());
String sql = "SELECT EMP_ID FROM EMPLOYEE_HISTORY"
+ " WHERE EMP_VAL2 = **This is where I want to use the variable in which the values of emp_id will be stored. There can be more than 100 emp_ids**"
+ " AND SYSDATE BETWEEN EMPHIST_START_DATE AND EMPHIST_END_DATE";
EWFMServiceInputData inData = (EWFMServiceInputData) inputData.get(i);
OverrideType ot = OverrideType.getOverrideType(inData.getRecordType());
logger.debug("override type = " + ot.toString());
logger.debug("inputData ["+i+"] = " + inData.toString());
OverrideAccess oa = new OverrideAccess(conn);
OverrideData ovr = new OverrideData();
ovr.setOvrUdf4(inData.getReferenceId().toString());
if (ovrAction.equals("APPLY")) {
ovr.setOvrStatus(OverrideData.PENDING);
Here I want to determine the Action. If it is Apply, then I need to find out the recordType. So basically branch it out for each recordType using if else statements or enum as I believe switch doesn't support Java 1.5 which is what I am using. Then for each recordType, I branch out and write the appropriate code corresponding to that recordType. If Action is CANCEL, then I just write the following code.
} else if (ovrAction.equals("CANCEL")) {
String sql = "SELECT * FROM OVERRIDE"
+ " WHERE OVR_UDF4 = ?"
+ " AND OVRTYP_ID = ?";
ps = conn.prepareStatement(sql);
rs = ps.executeQuery();
while (rs.next()); {
ovr.assignByName(rs);
ovr.setUpdated(false);
ovr.setRetrieved(true);
ovr.setOvrStatus(OverrideData.CANCEL);
oa.save(ovr);
}
}
ovr.setEmpId(empId);
String strOvrDate = inData.getOvrStartDate();
ovr.setOvrStartDate(DateHelper.parseDate(strOvrDate, "MM/dd/yyyy"));
if (ovrStartTime != null) {
ovr.setOvrStartTime(ovrTime);
}
Object ovrEndDate;
if (ovrEndDate != null) {
ovr.setOvrEndDate(ovrDate);
}
Object ovrEndTime;
if (ovrEndTime!= null) {
ovr.setOvrEndTime(ovrTime);
}
ovr.setOvrComment(inData.getOvrComments());
ovr.setWbuName(inData.getWbuName());
ovr.setWbuNameActual(inData.getWbuNameActual());
ovr.setOvrNewValue("VAC");
ovr.setOvrCreateDate(new Date());
ovr.setOvrtypId(103);
oa.insert(ovr);
RuleEngine.runCalcGroup(conn,
empId,
ovrDate,
ovrDate);
//COMMON BLOCK ENDS HERE
EWFMServiceData outData = new EWFMServiceData();
outData.setReferenceId(inData.getReferenceId());
String [] status = {"SUCCESS", "ERROR", "LOCKED", "EXCEPTION"};
Random ran = new Random();
String gen = status[ran.nextInt(status.length)];
logger.debug("Status is" + status );
outData.setStatus(gen);
if (gen.equals("SUCCESS")){
outData.setErrorDetails("");
} else if (gen.equals("ERROR")) {
outData.setErrorDetails("Usage of time code VAC is not allowed; balance is insufficient." + " error");
} else if (gen.equals("LOCKED")) {
outData.setErrorDetails("Timesheet cannot be edited because it is locked for payroll close." + "locked");
} else if (gen.equals("EXCEPTION")) {
outData.setErrorDetails("{ML}QR_INCORRECT_CONDITION_PARAMETER{/ML}Error in condition AWA Is Self Override Condition: java.lang.NullPointerException{ARGS}AWA Is Self Override Conditionjava.lang.NullPointerException{/ARGS" + "exception");
}
returnList.add(outData);
}
}catch (Exception e){
logger.error("Error occured",e);
throw new WSApplicationException("Error retrieved",e);
}finally{
SQLUtil.cleanUp(conn, ps, rs);
}
wsa.logOff();
logger.debug("inputData+ ");
return returnList;
}
// I need to know if writing enum is okay or can I just write a switch case above in the for loop and branch each override type and declare their individual variables there? What's the best way? Can someone help me with the code?
public enum OverrideType {
WORKDETAIL,
WORKPREMIUM,
EMPLOYEESCHEDULE,
EMPLOYEE;
public static OverrideType getOverrideType(String recordType) {
if(recordType == null) {
throw new IllegalArgumentException("Record Type cannot be null");
}
if(recordType.equals("Work Detail")) {
return WORKDETAIL;
} else if (recordType.equals("Work Premium")) {
return WORKPREMIUM;
} else if (recordType.equals("Schedule")) {
return EMPLOYEESCHEDULE;
} else if (recordType.equals("Shift Pattern")) {
return EMPLOYEE;
} else {
throw new IllegalArgumentException("Record Type cannot be" + recordType);
}
}
}
}
THE OTHER FIELDS I NEED TO INCLUDE ARE AS FOLLOWS:
FOR WORKDETAIL, I NEED TO USE TIMECODE OF FORMAT THAT IS SENT BY THE CLIENT.
FOR WORK PREMIUM, I NEED TO USE TIMECODE OF FORMAT THAT IS SENT BY THE CLIENT AND ANOTHER FIELD IS MINUTES THAT GIVES THE NUMBER OF MINUTES WHICH IS ALSO SENT BY THE CLIENT.
Generally, using enums is appropriate, especially if you have a defined set of possible types.
You can also add behavior to the enums, which could make your enum a little bit more sophisticated:
public enum OverrideType {
WORKDETAIL("Work Detail"),
WORKPREMIUM("Work Premium"),
EMPLOYEESCHEDULE("Schedule"),
EMPLOYEE("Shift Pattern");
private String identifier;
private OverrideType(String identifier){
this.identifier = identifier;
}
public static OverrideType getOverrideType(String recordType) {
if(recordType == null) {
throw new IllegalArgumentException("Record Type cannot be null");
}
for (OverrideType ot : OverrideType.values()) {
if (recordType.equals(ot.identifier)) {
return ot;
}
}
return null;
}
}
The following example shows how to use an interface in enums or an abstract method definition:
public enum OverrideType implements OverrideTypeIF {
WORKDETAIL("Work Detail") {
public int getKey() {
return 0;
}
},
WORKPREMIUM("Work Premium") {
public int getKey() {
return 0;
}
},
EMPLOYEESCHEDULE("Schedule") {
public int getKey() {
return 0;
}
},
EMPLOYEE("Shift Pattern") {
public int getKey() {
return 0;
}
public void myInterfaceMethod() {
// do type specific behavior
}
};
private String identifier;
private OverrideType(String identifier){
this.identifier = identifier;
}
public abstract int getKey();
public void myInterfaceMethod() {
// do default behavior
}
public static OverrideType getOverrideType(String recordType) {
if(recordType == null) {
throw new IllegalArgumentException("Record Type cannot be null");
}
for (OverrideType ot : OverrideType.values()) {
if (recordType.equals(ot.identifier)) {
return ot;
}
}
return null;
}
}
public interface OverrideTypeIF {
void myInterfaceMethod();
}

create adatabase through CreateDatabaseDocument() function

I want to create a database in ravendb , I used EnsureDatabaseExist() function. I am not able to use the function CreateDatabaseDocument() from namespace Raven.Client.Extensions and class is public static class MultiDatabase{} in my c# code. Intellisense in vs2010 not showing this function.
my code is :enter code here
public CreateDatabaseOpResult CreateDatabase(ConnectionOperationResult connection,string name)
{
DocumentDatabase database;
CreateDatabaseOpResult databaseOperationResult = new CreateDatabaseOpResult();
if (connection.IsOperationSuccessfull == true)
{
try
{
var doc = connection.documentStore.DatabaseCommands.ForDefaultDatabase();
var docId = doc.Get("Raven/Databases/" + name);
if (docId == null)
{
//static class
//multidatabase
connection.documentStore.DatabaseCommands.EnsureDatabaseExists(name);
}
else
{
databaseOperationResult.IsOperationSuccessfull = false;
throw new ArgumentException("Database already exists");
}
databaseOperationResult.IsOperationSuccessfull = true;
databaseOperationResult.database = database;
}
//and i want to use this function from
namespace Raven.Client.Extensions
{
///<summary>
/// Methods to create mutli tenants databases
///</summary>
public static class MultiDatabase
{
public static RavenJObject CreateDatabaseDocument(string name)
{
AssertValidName(name);
var doc = RavenJObject.FromObject(new DatabaseDocument
{
Settings =
{
{"Raven/DataDir", Path.Combine("~", Path.Combine("Tenants", name))}
}
});
doc.Remove("Id");
return doc;
}
thanks in advance...:)
CreateDatabase is an internal method which just returned the database document. EnsureDatabaseExist used this method and also stores that document if it doesn't exists.
You should use EnsureDatabaseExist method.

Apex Test Class - How to set a rollup count field in a test class

Trying to set a value for a roll up summary field in the test class to improve code coverage. How do I do it?
public class clsPreferredIASetExt {
List<PreferredIA__c> preferredias;
public static PreferredIA__c[] tobeClosed = new PreferredIA__c[0];
public static PreferredIA__c[] newPreIAs = new PreferredIA__c[0];
public static PreferredIA__c loopadd;
public static PreferredContact__c[] contactlists = new PreferredContact__c[0];
public static Account[] InvoicedAccounts = new Account[0];
public static PreferredIA__c[] monkey;
public clspreferrediaSetExt(ApexPages.StandardSetController controller) {
preferredias = (List<PreferredIA__c>) controller.getSelected();
}
public void getInitCloseInv() {
tobeclosed = [select id, Account__c, Account__r.id, Account__r.Name,
Account__r.AccountNumber, Specialist__c,
PreferredInvoice__c, Status__c
from PreferredIA__c where Status__c = 'Invoiced' limit 150];
list<string> testme = new list<string>{};
for(PreferredIA__c a:tobeclosed) {
testme.add(a.Account__r.id);
}
InvoicedAccounts = [select id, EligibleIAs__c, PreferredOverride__c,
Preferred_Territory__r.rep__c, LastSurveyDate__c,
InitialInspectionComplete__c, Program_level__c,
PreferredExempt__c, Account_Status__c,
Active_IAs__c, Last_Training__c
from Account where id IN :testme];
Contactlists = [select id, Account__c
from PreferredContact__c where Account__c IN :testme];
for(PreferredIA__c q:tobeclosed) {
q.Status__c = 'Closed';
}
for(Account z:invoicedaccounts) {
/****************************************************************
The following condition is where I am trying to set the z.EligibleIAs__c
which is a roll up count field of PreferredIA__c objects associated with
the account.
****************************************************************/
if(z.EligibleIAs__c == 0
&& z.Program_Level__c == 'Preferred'
&& !z.PreferredExempt__c
&& (z.Account_Status__c == 'Active'
|| z.Account_Status__c == 'Product Only')) {
loopadd = new PreferredIA__c();
system.debug(z.id);
system.debug(z.Account_Status__c);
loopadd.Account__c = z.id;
if(z.PreferredOverride__c != null) {
loopadd.Specialist__c = z.PreferredOverride__c;
}
else {
loopadd.Specialist__c= z.Preferred_territory__r.Rep__c;
}
for(PreferredContact__c q:contactlists) {
if(q.Account__c == z.id) {
loopadd.PreferredContact__c = q.id;
}
}
loopadd.CreatedDate__c = Date.Today();
if(z.Last_training__c != null) {
loopadd.DueDate__c = z.Last_Training__c.AddDays(365);
}
else {
loopadd.DueDate__c = Date.Today().AddDays(365);
}
loopadd.initial__c = false;
loopadd.Status__c = 'Unacknowledged';
newPreIAs.add(loopadd);
}
z.InitialInspectionComplete__c = true;
}
try {
update tobeclosed;
update invoicedaccounts;
insert newPreIAs;
}
catch(system.dmlexception q) {
system.debug(q);
system.debug(invoicedaccounts);
system.debug(newPreIAs);
}
}
public void ReceivePPW() {
monkey = [select id, Status__c from PreferredIA__c
where id in :preferredias and status__c = 'Training Completed'];
for (PreferredIA__c m:monkey) {
m.status__c = 'Awaiting Invoice';
}
update monkey;
}
}
I can't actually see where you're trying to write to the field — or did you remove it because it wasn't working?
That aside, the answer is that you can not write to a roll-up summary field. If you require a value in that field you should insert child records to your parent test records, with appropriate field values such that your summary field calculates a value.
Also, I can see that you're querying PerferredIA__c at the start, your test methods should never depend on data being in the system already, you should insert your records yourself in your test code. The reason for this is that if you try to deploy to an org which has no relevant data, your tests will fail and so, subsequently, will your deployment.
For situations like these, consider mock objects (or just variables simulating expected values), similar to inserting test values as Lacey suggests. This technique is required to achieve 100% coverage when doing callouts, for example, since they terminate tests at the moment of the call.

Manually map column names with class properties

I am new to the Dapper micro ORM. So far I am able to use it for simple ORM related stuff but I am not able to map the database column names with the class properties.
For example, I have the following database table:
Table Name: Person
person_id int
first_name varchar(50)
last_name varchar(50)
and I have a class called Person:
public class Person
{
public int PersonId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
Please note that my column names in the table are different from the property name of the class to which I am trying to map the data which I got from the query result.
var sql = #"select top 1 PersonId,FirstName,LastName from Person";
using (var conn = ConnectionFactory.GetConnection())
{
var person = conn.Query<Person>(sql).ToList();
return person;
}
The above code won't work as the column names don't match the object's (Person) properties. In this scenario, is there anything i can do in Dapper to manually map (e.g person_id => PersonId) the column names with object properties?
Dapper now supports custom column to property mappers. It does so through the ITypeMap interface. A CustomPropertyTypeMap class is provided by Dapper that can do most of this work. For example:
Dapper.SqlMapper.SetTypeMap(
typeof(TModel),
new CustomPropertyTypeMap(
typeof(TModel),
(type, columnName) =>
type.GetProperties().FirstOrDefault(prop =>
prop.GetCustomAttributes(false)
.OfType<ColumnAttribute>()
.Any(attr => attr.Name == columnName))));
And the model:
public class TModel {
[Column(Name="my_property")]
public int MyProperty { get; set; }
}
It's important to note that the implementation of CustomPropertyTypeMap requires that the attribute exist and match one of the column names or the property won't be mapped. The DefaultTypeMap class provides the standard functionality and can be leveraged to change this behavior:
public class FallbackTypeMapper : SqlMapper.ITypeMap
{
private readonly IEnumerable<SqlMapper.ITypeMap> _mappers;
public FallbackTypeMapper(IEnumerable<SqlMapper.ITypeMap> mappers)
{
_mappers = mappers;
}
public SqlMapper.IMemberMap GetMember(string columnName)
{
foreach (var mapper in _mappers)
{
try
{
var result = mapper.GetMember(columnName);
if (result != null)
{
return result;
}
}
catch (NotImplementedException nix)
{
// the CustomPropertyTypeMap only supports a no-args
// constructor and throws a not implemented exception.
// to work around that, catch and ignore.
}
}
return null;
}
// implement other interface methods similarly
// required sometime after version 1.13 of dapper
public ConstructorInfo FindExplicitConstructor()
{
return _mappers
.Select(mapper => mapper.FindExplicitConstructor())
.FirstOrDefault(result => result != null);
}
}
And with that in place, it becomes easy to create a custom type mapper that will automatically use the attributes if they're present but will otherwise fall back to standard behavior:
public class ColumnAttributeTypeMapper<T> : FallbackTypeMapper
{
public ColumnAttributeTypeMapper()
: base(new SqlMapper.ITypeMap[]
{
new CustomPropertyTypeMap(
typeof(T),
(type, columnName) =>
type.GetProperties().FirstOrDefault(prop =>
prop.GetCustomAttributes(false)
.OfType<ColumnAttribute>()
.Any(attr => attr.Name == columnName)
)
),
new DefaultTypeMap(typeof(T))
})
{
}
}
That means we can now easily support types that require map using attributes:
Dapper.SqlMapper.SetTypeMap(
typeof(MyModel),
new ColumnAttributeTypeMapper<MyModel>());
Here's a Gist to the full source code.
This works fine:
var sql = #"select top 1 person_id PersonId, first_name FirstName, last_name LastName from Person";
using (var conn = ConnectionFactory.GetConnection())
{
var person = conn.Query<Person>(sql).ToList();
return person;
}
Dapper has no facility that allows you to specify a Column Attribute, I am not against adding support for it, providing we do not pull in the dependency.
For some time, the following should work:
Dapper.DefaultTypeMap.MatchNamesWithUnderscores = true;
I do the following using dynamic and LINQ:
var sql = #"select top 1 person_id, first_name, last_name from Person";
using (var conn = ConnectionFactory.GetConnection())
{
List<Person> person = conn.Query<dynamic>(sql)
.Select(item => new Person()
{
PersonId = item.person_id,
FirstName = item.first_name,
LastName = item.last_name
}
.ToList();
return person;
}
Here is a simple solution that doesn't require attributes allowing you to keep infrastructure code out of your POCOs.
This is a class to deal with the mappings. A dictionary would work if you mapped all the columns, but this class allows you to specify just the differences. In addition, it includes reverse maps so you can get the field from the column and the column from the field, which can be useful when doing things such as generating sql statements.
public class ColumnMap
{
private readonly Dictionary<string, string> forward = new Dictionary<string, string>();
private readonly Dictionary<string, string> reverse = new Dictionary<string, string>();
public void Add(string t1, string t2)
{
forward.Add(t1, t2);
reverse.Add(t2, t1);
}
public string this[string index]
{
get
{
// Check for a custom column map.
if (forward.ContainsKey(index))
return forward[index];
if (reverse.ContainsKey(index))
return reverse[index];
// If no custom mapping exists, return the value passed in.
return index;
}
}
}
Setup the ColumnMap object and tell Dapper to use the mapping.
var columnMap = new ColumnMap();
columnMap.Add("Field1", "Column1");
columnMap.Add("Field2", "Column2");
columnMap.Add("Field3", "Column3");
SqlMapper.SetTypeMap(typeof (MyClass), new CustomPropertyTypeMap(typeof (MyClass), (type, columnName) => type.GetProperty(columnMap[columnName])));
An easy way to achieve this is to just use aliases on the columns in your query.
If your database column is PERSON_ID and your object's property is ID, you can just do
select PERSON_ID as Id ...
in your query and Dapper will pick it up as expected.
Taken from the Dapper Tests which is currently on Dapper 1.42.
// custom mapping
var map = new CustomPropertyTypeMap(typeof(TypeWithMapping),
(type, columnName) => type.GetProperties().FirstOrDefault(prop => GetDescriptionFromAttribute(prop) == columnName));
Dapper.SqlMapper.SetTypeMap(typeof(TypeWithMapping), map);
Helper class to get name off the Description attribute (I personally have used Column like #kalebs example)
static string GetDescriptionFromAttribute(MemberInfo member)
{
if (member == null) return null;
var attrib = (DescriptionAttribute)Attribute.GetCustomAttribute(member, typeof(DescriptionAttribute), false);
return attrib == null ? null : attrib.Description;
}
Class
public class TypeWithMapping
{
[Description("B")]
public string A { get; set; }
[Description("A")]
public string B { get; set; }
}
Before you open the connection to your database, execute this piece of code for each of your poco classes:
// Section
SqlMapper.SetTypeMap(typeof(Section), new CustomPropertyTypeMap(
typeof(Section), (type, columnName) => type.GetProperties().FirstOrDefault(prop =>
prop.GetCustomAttributes(false).OfType<ColumnAttribute>().Any(attr => attr.Name == columnName))));
Then add the data annotations to your poco classes like this:
public class Section
{
[Column("db_column_name1")] // Side note: if you create aliases, then they would match this.
public int Id { get; set; }
[Column("db_column_name2")]
public string Title { get; set; }
}
After that, you are all set. Just make a query call, something like:
using (var sqlConnection = new SqlConnection("your_connection_string"))
{
var sqlStatement = "SELECT " +
"db_column_name1, " +
"db_column_name2 " +
"FROM your_table";
return sqlConnection.Query<Section>(sqlStatement).AsList();
}
Messing with mapping is borderline moving into real ORM land. Instead of fighting with it and keeping Dapper in its true simple (fast) form, just modify your SQL slightly like so:
var sql = #"select top 1 person_id as PersonId,FirstName,LastName from Person";
If you're using .NET 4.5.1 or higher checkout Dapper.FluentColumnMapping for mapping the LINQ style. It lets you fully separate the db mapping from your model (no need for annotations)
This is piggy backing off of other answers. It's just a thought I had for managing the query strings.
Person.cs
public class Person
{
public int PersonId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public static string Select()
{
return $"select top 1 person_id {nameof(PersonId)}, first_name {nameof(FirstName)}, last_name {nameof(LastName)}from Person";
}
}
API Method
using (var conn = ConnectionFactory.GetConnection())
{
var person = conn.Query<Person>(Person.Select()).ToList();
return person;
}
The simple solution to the problem Kaleb is trying to solve is just to accept the property name if the column attribute doesn't exist:
Dapper.SqlMapper.SetTypeMap(
typeof(T),
new Dapper.CustomPropertyTypeMap(
typeof(T),
(type, columnName) =>
type.GetProperties().FirstOrDefault(prop =>
prop.GetCustomAttributes(false)
.OfType<ColumnAttribute>()
.Any(attr => attr.Name == columnName) || prop.Name == columnName)));
The easier way (same as #Matt M's answer but corrected and added fallback to default map)
// override TypeMapProvider to return custom map for every requested type
Dapper.SqlMapper.TypeMapProvider = type =>
{
// create fallback default type map
var fallback = new DefaultTypeMap(type);
return new CustomPropertyTypeMap(type, (t, column) =>
{
var property = t.GetProperties().FirstOrDefault(prop =>
prop.GetCustomAttributes(typeof(ColumnAttribute))
.Cast<ColumnAttribute>()
.Any(attr => attr.Name == column));
// if no property matched - fall back to default type map
if (property == null)
{
property = fallback.GetMember(column)?.Property;
}
return property;
});
};
for all of you who use Dapper 1.12, Here's what you need to do to get this done:
Add a new column attribute class:
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property]
public class ColumnAttribute : Attribute
{
public string Name { get; set; }
public ColumnAttribute(string name)
{
this.Name = name;
}
}
Search for this line:
map = new DefaultTypeMap(type);
and comment it out.
Write this instead:
map = new CustomPropertyTypeMap(type, (t, columnName) =>
{
PropertyInfo pi = t.GetProperties().FirstOrDefault(prop =>
prop.GetCustomAttributes(false)
.OfType<ColumnAttribute>()
.Any(attr => attr.Name == columnName));
return pi != null ? pi : t.GetProperties().FirstOrDefault(prop => prop.Name == columnName);
});
I know this is a relatively old thread, but I thought I'd throw what I did out there.
I wanted attribute-mapping to work globally. Either you match the property name (aka default) or you match a column attribute on the class property. I also didn't want to have to set this up for every single class I was mapping to. As such, I created a DapperStart class that I invoke on app start:
public static class DapperStart
{
public static void Bootstrap()
{
Dapper.SqlMapper.TypeMapProvider = type =>
{
return new CustomPropertyTypeMap(typeof(CreateChatRequestResponse),
(t, columnName) => t.GetProperties().FirstOrDefault(prop =>
{
return prop.Name == columnName || prop.GetCustomAttributes(false).OfType<ColumnAttribute>()
.Any(attr => attr.Name == columnName);
}
));
};
}
}
Pretty simple. Not sure what issues I'll run into yet as I just wrote this, but it works.
Kaleb Pederson's solution worked for me. I updated the ColumnAttributeTypeMapper to allow a custom attribute (had requirement for two different mappings on same domain object) and updated properties to allow private setters in cases where a field needed to be derived and the types differed.
public class ColumnAttributeTypeMapper<T,A> : FallbackTypeMapper where A : ColumnAttribute
{
public ColumnAttributeTypeMapper()
: base(new SqlMapper.ITypeMap[]
{
new CustomPropertyTypeMap(
typeof(T),
(type, columnName) =>
type.GetProperties( BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance).FirstOrDefault(prop =>
prop.GetCustomAttributes(true)
.OfType<A>()
.Any(attr => attr.Name == columnName)
)
),
new DefaultTypeMap(typeof(T))
})
{
//
}
}

Updating properties for multiple users

How do I update a list of different Telephone, IPPhone using this
static void Main(string[] args)
{
Console.Write("Enter userid : "); // I would pass this in from the first
//Field in the .csv file 2439009
String username = Console.ReadLine();
try
{
DirectoryEntry myLdapConnection = createDirectoryEntry();
DirectorySearcher search = new DirectorySearcher(myLdapConnection);
search.Filter = "(cn=" + uid + ")";
search.PropertiesToLoad.Add("Telephone","IPPhone");
SearchResult result = search.FindOne();
if (result != null)
{
// create new object from search result
DirectoryEntry entryToUpdate = result.GetDirectoryEntry();
// show existing title
Console.WriteLine("Current title : " + entryToUpdate.Properties["Telephone][0].ToString());
Console.Write("\n\nEnter new title : ");
// get new title and write to AD
String newTitle = Console.ReadLine();
entryToUpdate.Properties["Telephone"].Value = newTelePhone;
entryToUpdate.Properties["IPPhone"].Value = newIPPhone;
entryToUpdate.CommitChanges();
Console.WriteLine("\n\n...new title saved");
}
else Console.WriteLine("User not found!");
}
catch (Exception e)
{
Console.WriteLine("Exception caught:\n\n" + e.ToString());
}
}
static DirectoryEntry createDirectoryEntry()
{
// create and return new LDAP connection with desired settings
DirectoryEntry ldapConnection = new DirectoryEntry("mydomain.dm.com");
ldapConnection.Path = "LDAP://OU=myusers,DC=sales,DC=US,DC=US";
ldapConnection.AuthenticationType = AuthenticationTypes.Secure;
return ldapConnection;
}
I'm guessing you've grabbed someone else's code and don't know how to use it?
You should understand that this code can (will?) cause serious server problems as the DirectoryEntry resources are not closed correctly.
Every DirectoryEntry variable in your Main method should be wrapped in a using(){} statement.
Try something like this:
You define a class CSVRecord which holds your data from the CSV - read that in using FileHelpers. The class looks like this:
public class CSVRecord
{
public string EmployeeNumber { get; set; }
public string TelephoneNumber { get; set; }
public string IPPhoneNumber { get; set; }
}
Once you've read that class in, you need to iterate over its elements, and do the update for each of them.
CSVRecord[] listOfEmployees = (read in via FileHelpers)
// define root for searching your user accounts
using (DirectoryEntry root = new DirectoryEntry("LDAP://dc=yourcompany,dc=com"))
{
// set up directory searcher to find users by employeeId
using (DirectorySearcher searcher = new DirectorySearcher(root))
{
searcher.SearchScope = SearchScope.Subtree;
// iterate over all entries in your list of employees
foreach (CSVRecord csvEntry in listOfEmployees)
{
searcher.Filter = string.Format("(&(objectCategory=user)(employeeId={0}))", csvEntry.EmployeeNumber);
// search for that employee
SearchResult result = searcher.FindOne();
// if found - access the DirectoryEntry
if (result != null)
{
DirectoryEntry foundUser = result.GetDirectoryEntry();
// update properties from values in CSV
foundUser.Properties["telephoneNumber"].Value = csvEntry.TelephoneNumber;
foundUser.Properties["ipPhone"].Value = csvEntry.IPPhoneNumber;
// save changes back to directory
foundUser.CommitChanges();
}
}
}
}
Does that work for you??

Resources