Родовое сопоставление объекта с параметризованным конструктором
У меня есть слой доступа к данным, который возвращает IDataRecord. У меня есть служба WCF, которая обслуживает DataContracts (dto's). Эти DataContracts инициируются параметризованным конструктором, содержащим IDataRecord следующим образом:
[DataContract]
public class DataContractItem
{
[DataMember]
public int ID;
[DataMember]
public string Title;
public DataContractItem(IDataRecord record)
{
this.ID = Convert.ToInt32(record["ID"]);
this.Title = record["title"].ToString();
}
}
К сожалению, я не могу изменить DAL, поэтому я обязан работать с IDataRecord в качестве ввода. Но в целом это работает очень хорошо. Сопоставления в большинстве случаев довольно просты, иногда они немного сложнее, но это не ракетостроение.
Однако теперь я хотел бы иметь возможность использовать обобщенные значения для создания экземпляров различных DataContracts для упрощения методов обслуживания WCF. Я хочу быть в состоянии сделать что-то вроде:
public T DoSomething<T>(IDataRecord record) {
...
return new T(record);
}
Поэтому я попытался следующие решения:
Используйте универсальный типизированный интерфейс с конструктором. не работает: конечно, мы не можем определить конструктор в интерфейсе
Используйте статический метод для создания экземпляра DataContract и создания типизированного интерфейса, содержащего этот статический метод. не работает: конечно, мы не можем определить статический метод в интерфейсе
Использовать универсальный типизированный интерфейс, содержащий ограничение new(), не работает: ограничение new() не может содержать параметр (IDataRecord)
Использование объекта фабрики для выполнения сопоставления на основе типа DataContract. работает, но: не очень чисто, потому что теперь у меня есть оператор switch со всеми отображениями в одном файле.
Я не могу найти действительно чистого решения для этого. Может кто-нибудь пролить свет на это для меня? Проект слишком мал для каких-либо сложных методов отображения и слишком велик для реализации на основе коммутатора.
3 ответа
Я думаю, что вы могли бы использовать AutoMapper для этого. Это проект с открытым исходным кодом, и он позволит делать то, что вы хотите. В вашей сделке что-то вы сделаете так:
public TDestination DoSomething<TDestination>(IDataRecord record) {
return Mapper.Map(reader, reader.GetType(), typeof(TDestination));
}
Прежде чем вы сможете это сделать, вы должны создать карту
Mapper.CreateMap<IDataRecord, MyDestinationType>()
Вы можете вызвать конструктор типов через отражение:
public T DoSomething<T>(IDataRecord record)
{
//do something...
var ci = typeof(T).GetConstructor(new[] { typeof(IDataRecord) });
return (T)ci.Invoke(new object[] { record });
}
РЕДАКТИРОВАТЬ: вышеупомянутый подход является довольно хрупким и опирается на соглашение, поэтому другой подход будет заключаться в создании интерфейса для ваших контрактов данных, который позволяет инициализацию:
public interface IDataContract
{
void Initialise(IDataRecord record);
}
public T DoSomething<T>(IDataRecord record) where T : IDataContract, new()
{
//do something
T contract = new T();
contract.Initialise(record);
return contract;
}
Вот как я это делаю..
Методы расширения для DataRow, которые позволяют мне преобразовывать бизнес-объект в DataRow и наоборот.
/// <summary>
/// Extension methods for DataRow.
/// </summary>
public static class DataRowExtensions
{
/// <summary>
/// Converts DataRow into business object.
/// </summary>
/// <typeparam name="TEntity">A type that inherits EntityBase.</typeparam>
/// <param name="dataRow">DataRow object to convert to business object.</param>
/// <returns>business object created from DataRow.</returns>
public static TEntity ToEntity<TEntity>(this DataRow dataRow)
where TEntity : EntityBase, new()
{
TEntity entity = new TEntity();
ExtensionHelper.TransformDataRowToEntity<TEntity, DataRow>(ref dataRow, ref entity);
return entity;
}
/// <summary>
/// Converts business object into DataRow.
/// </summary>
/// <typeparam name="TEntity">A type that inherits EntityBase.</typeparam>
/// <param name="dataRow">DataRow object to convert business object into.</param>
/// <param name="entity">Business object which needs to be converted to DataRow.</param>
public static void FromEntity<TEntity>(this DataRow dataRow, TEntity entity)
where TEntity : EntityBase, new()
{
ExtensionHelper.TransformEntityToDataRow<TEntity, DataRow>(ref entity, ref dataRow);
}
}
Фактические вспомогательные методы, которые выполняют преобразование..
/// <summary>
/// Helper methods for transforming data objects into business objects and vice versa.
/// </summary>
/// <remarks>
/// <para>Most important implementation that takes care of universal transformation between business objects and data object.</para>
/// <para>Saves programmers from writing the same old code for every object in the system.</para>
/// </remarks>
public static class ExtensionHelper
{
/// <summary>
/// Transforms business object into DataRow.
/// </summary>
/// <typeparam name="TEntity">A type that inherits EntityBase.</typeparam>
/// <typeparam name="TDataRow">A type that inherits DataRow.</typeparam>
/// <param name="entity">business object which is transformed into DataRow object.</param>
/// <param name="dataRow">DataRow object which is transformed from business object.</param>
public static void TransformEntityToDataRow<TEntity, TDataRow>(ref TEntity entity, ref TDataRow dataRow)
where TDataRow : DataRow
where TEntity : EntityBase
{
IQueryable<DataField> entityFields = entity.GetDataFields();
foreach (DataColumn dataColoumn in dataRow.Table.Columns)
{
if (!dataColoumn.ReadOnly)
{
var entityField =
entityFields.Single(e => e.DataFieldMapping.MappedField.Equals(dataColoumn.ColumnName, StringComparison.OrdinalIgnoreCase));
if (entityField.Property.GetValue(entity, null) == null)
{
if (dataColoumn.AllowDBNull)
{
dataRow[dataColoumn] = System.DBNull.Value;
}
else
{
throw new Exception(dataColoumn.ColumnName + " cannot have null value.");
}
}
else
{
if (entityField.Property.GetType().IsEnum)
{
dataRow[dataColoumn] = Convert.ToByte(entityField.Property.GetValue(entity, null));
}
else
{
dataRow[dataColoumn] = entityField.Property.GetValue(entity, null);
}
}
}
}
}
/// <summary>
/// Transforms DataRow into business object.
/// </summary>
/// <typeparam name="TEntity">A type that inherits EntityBase.</typeparam>
/// <typeparam name="TDataRow">A type that inherits DataRow.</typeparam>
/// <param name="dataRow">DataRow object which is transformed from business object.</param>
/// <param name="entity">business object which is transformed into DataRow object.</param>
public static void TransformDataRowToEntity<TEntity, TDataRow>(ref TDataRow dataRow, ref TEntity entity)
where TDataRow : DataRow
where TEntity : EntityBase
{
IQueryable<DataField> entityFields = entity.GetDataFields();
foreach (var entityField in entityFields)
{
if (dataRow[entityField.DataFieldMapping.MappedField] is System.DBNull)
{
entityField.Property.SetValue(entity, null, null);
}
else
{
if (entityField.Property.GetType().IsEnum)
{
Type enumType = entityField.Property.GetType();
EnumConverter enumConverter = new EnumConverter(enumType);
object enumValue = enumConverter.ConvertFrom(dataRow[entityField.DataFieldMapping.MappedField]);
entityField.Property.SetValue(entity, enumValue, null);
}
else
{
entityField.Property.SetValue(entity, dataRow[entityField.DataFieldMapping.MappedField], null);
}
}
}
}
}
А вот как выглядит мой образец бизнес-объекта. Пожалуйста, обратите внимание на пользовательские атрибуты.
/// <summary>
/// Represents User.
/// </summary>
[TableMapping("Users", "User", "Users")]
public class User : EntityBase
{
#region Constructor(s)
/// <summary>
/// Initializes a new instance of the User class.
/// </summary>
public User()
{
}
#endregion
#region Properties
#region Default Properties - Direct Field Mapping using DataFieldMappingAttribute
/// <summary>
/// Gets or sets the ID value of the AppUser object.
/// </summary>
[DataFieldMapping("UserID")]
[DataObjectFieldAttribute(true, true, false)]
[NotNullOrEmpty(Message = "UserID From UserDetails Table Is Required.")]
public override int Id
{
get;
set;
}
/// <summary>
/// Gets or sets the Username value of the User object.
/// </summary>
[DataFieldMapping("UserName")]
[Searchable]
[NotNullOrEmpty(Message = "Username Is Required.")]
public string UserName
{
get;
set;
}
/// <summary>
/// Gets or sets the FirstName value of the AppUser object.
/// </summary>
[DataFieldMapping("FirstName")]
[Searchable]
public string FirstName
{
get;
set;
}
/// <summary>
/// Gets or sets the LastName value of the AppUser object.
/// </summary>
[DataFieldMapping("LastName")]
[Searchable]
public string LastName
{
get;
set;
}
/// <summary>
/// Gets or sets the WebSite value of the AppUser object.
/// </summary>
[DataFieldMapping("WebSite")]
[ValidURL(Message = "Website is not in Proper Format.")]
public string WebSite
{
get;
set;
}
/// <summary>
/// Gets or sets the ContactNumber value of the AppUser object.
/// </summary>
[DataFieldMapping("ContactNumber")]
[Searchable]
public string ContactNumber
{
get;
set;
}
/// <summary>
/// Gets or sets a value indicating whether AppUser Object is active or inactive.
/// </summary>
[DataFieldMapping("IsActive")]
public bool IsActive
{
get;
set;
}
/// <summary>
/// Gets or sets the BirthDate value of the AppUser object.
/// </summary>
[DataFieldMapping("BirthDate")]
public DateTime? BirthDate
{
get;
set;
}
#region Derived Properties
/// <summary>
/// Gets the full name of the AppUser
/// </summary>
public string FullName
{
get { return this.FirstName + " " + this.LastName; }
}
/// <summary>
/// Gets the Age value of the AppUser
/// </summary>
public int Age
{
get { return this.BirthDate.HasValue ? this.BirthDate.Value.AgeInYears() : 0; }
}
#endregion
#endregion
}
А вот и мой клиентский код..
/// <summary>
/// Gets User object by user name.
/// </summary>
/// <param name="username">UserName of the user</param>
/// <returns>User Object</returns>
public static User GetUserByUsername(string username)
{
try
{
return Adapter.GetUserByUserName(username)[0].ToEntity<User>();
}
catch
{
return null;
}
}