gpt4 book ai didi

c# - 对于存储在 ini 文件中的信息,什么是好的 Mapper 模式?

转载 作者:行者123 更新时间:2023-11-30 22:34:56 25 4
gpt4 key购买 nike

在设计 n 层应用程序时,我倾向于使用采用并改编自 Lhotka 的 CSLA 框架的模式。简而言之,Repository 层填充一个 SqlDataReader 并将数据读取器、要映射的实例和映射信息传递给 Mapper 层,然后 Mapper 层填充该实例。

由于多种原因,这种模式在我从事的许多项目中一次又一次地证明了自己:

  1. 它易于理解和实现,开发人员倾向于坚持使用它,因为他们了解它在做什么。
  2. 它可以非常轻松地重用继承类和复合类的映射器。
  3. 实体中的任何更改,以及分配给它的映射器,都会导致编译时错误直接指向也需要更改的存储过程。

这里是一些示例存储库代码:

internal static List<CompositeEntities.ContactReportRpa> RetrieveByReportId(int reportId)
{
CompositeEntities.ContactReportRpa report = null;
List<CompositeEntities.ContactReportRpa> reports = new List<CompositeEntities.ContactReportRpa>();
using (SqlConnection conn = DBConnection.GetConnection())
{
cmd.Connection = conn;
cmd.CommandType = System.Data.CommandType.StoredProcedure;
cmd.CommandText = "ContactReportRpa_SEL_ByIdReport";
cmd.Parameters.Add("@IdReport", System.Data.SqlDbType.Int).Value = reportId;
conn.Open();
using (SqlDataReader reader = cmd.ExecuteReader())
{
while (reader.Read())
{
report = new CompositeEntities.ContactReportRpa();
ContactReportRpaMapper.Map("IdReportRpa", "IdReport", "IdRecommendation", "IsDisplayed", "Comments", report.Rpa, reader);
RpaRecommendationMapper.Map("IdRecommendation", "IdDepartment", "TitleRecommendation", "Description", "DisplayOrderRecommendation", report.Recommendation, reader);
RpaDepartmentMapper.Map("IdDepartment", "TitleDepartment", "DisplayOrderDepartment", report.Department, reader);
reports.Add(report);
}
}
}
return reports;
}

这是一些示例映射器代码。这非常简单:映射器知道哪个类属性被映射到数据读取器中的每个字段。每个字段的名称都传递给映射器,因此无论分配给存储过程中每个字段的名称如何,都可以使用相同的映射器。

internal static void Map(string fieldId, string fieldName, string fieldDisplayOrder,   RpaDepartment entity, SqlDataReader reader)
{
entity.Id = reader.GetInt32(reader.GetOrdinal(fieldId));
entity.Title = reader.GetString(reader.GetOrdinal(fieldName));
entity.DisplayOrder = reader.GetInt32(reader.GetOrdinal(fieldDisplayOrder));
}

所以我的问题是:当数据源是文本文件时,我应该如何实现这种模式?我想坚持这种模式,因为最终数据源将迁移到数据库。

有什么建议吗?

编辑:ini 文件已经存在,此时我无法继续更改它们。所以我现在坚持使用它们。

最佳答案

在 ini 文件中创建良好的数据层次结构有点困难。这就是 MS 似乎主要迁移到 XML 文件的部分原因。查看此问题的答案:Reading/writing an INI file

如果您选择 XML 选项,我将跳过这些映射内容,并在使用 XPath 找到合适的 XML 后直接将您的对象序列化。那么您就不需要映射器了。

你也可以选择 an in-memory or file-based DB, like SqLite . Perf 会很棒,而且您的部署占用空间非常小。

另外,我建议避免尝试抽象这个映射器的东西,因为我认为它不能很好地在 DB 和 ini 文件之间转换。如果你看看那里许多 ORM 库的复杂性,你就会发现这种映射真的有多么困难。映射级别的大多数概念根本无法很好地转换为 ini 文件。将映射(存储库)的是更高级别的概念,这就是我发布原始答案的原因(见下文)。

但是如果你想保持你正在使用的模式,你的 ini 文件看起来像这样:

[Report.3]
IdReport = 3
IdReportRpas = 7,13

[ReportRpa.7]
IdReportRpa = 7
IdReport = 3
IdRecommendation = 12
IsDisplayed = true
Comments = I'm not sure what an RPA is...

[ReportRpa.13]
IdReportRpa = 13
IdReport = 3
; ... and rest of properties here

[Recommendation.12]
IdRecommendation = 12
IdDepartment = 33
TitleRecommendation = Some Recommendation
Description = Some Recommendation Description
DisplayOrderRecommendation = 0

[Department.33]
IdDepartment = 33
TitleDepartment = Bureau of DBs and ini files
DisplayOrderDepartment = 0

...然后您可以简单地编写您的存储库以从 ini 部分获取数据,并编写您的映射器来查看每个 ini 值,就像您当前查看结果集中的列一样。

using(var iniFileReader = new IniFileReader())
{
string reportSectionName = string.Format("Report.{0}", contactId);
var reportSection = iniFileReader.GetSection(reportSectionName);

// Todo: Abstract this sort of procedure/enumeration stuff out.
// Similar to the existing code's stored procedure call
int[] idReportRpas = reportSection.GetValue(IdReportRpas)
.Split(',')
.Select(s => int.Parse(s);

foreach(string idReportRpa in idReportRpas)
{
report = new CompositeEntities.ContactReportRpa();

string rpaSectionName = string.Format("ReportRpa.{0}", idReportRpa);
var rpaSection = iniFileReader.GetSection(rpaSectionName);

ContactReportRpaMapper.Map("IdReportRpa", "IdReport", "IdRecommendation",
"IsDisplayed", "Comments", report.Rpa, rpaSection);

// ...
}
}

您当前的映射器代码绑定(bind)到您的存储类型,因此您需要提出一个更通用的映射器接口(interface)。或者使最后一个 reader 参数更通用以支持两种映射类型(在您的情况下,reader,在我的情况下,每个 ini 部分实例)。例如:

public interface IDataValueReader
{
// Signature is one that might be able to support ini files:
// string -> string; then cast
//
// As well as a DB reader:
// string -> strongly typed object
T ReadValue<T>(string valueName);
}

public class DbDataReader : IDataValueReader
{
private readonly SqlDataReader reader;

public DbDataReader(SqlDataReader reader)
{
this.reader = reader;
}

object ReadValue<T>(string fieldId)
{
return (T)reader.GetObject(reader.GetOrdinal(fieldId));
}
}

public class IniDataSectionReader : IDataValueReader
{
private readonly IniFileSection fileSection;

public IniDataSectionReader(IniFileSection fileSection)
{
this.fileSection = fileSection;
}

object ReadValue<T>(string valueName)
{
return (T)Convert.ChangeType(fileSection.GetValue(fieldId), typeof(T));
}
}

请注意,这都是自定义代码 - 没有官方的 ini 文件阅读器,我也没有尝试过,所以我无法就使用哪个第三方库提出建议。 That question I linked at the top有一些建议。

原始答案

(部分内容可能还有用)

为你的 repository 创建一个接口(interface) ,并确保您的代码的更高层仅通过此接口(interface)与您的数据存储对话。

示例界面(您的可能不同):

public interface IReportRepository
{
void Create(Report report);
Report Read(int id);
void Update(Report report);
void Delete(Report report);
}

如果需要,您也可以使该接口(interface)通用。

为了确保更高层只知道存储库,您可以在IReportRepository 的实现中构建与文件/数据库对话的类,或使用Dependency Injection填充它。但是无论您做什么,除了IRepository 和您的个人数据实体(Report)之外,不要让您的更高级别的代码知道任何信息。

您可能还想查看 the Unit of Work pattern ,并在那里包装实际的数据访问。这样您就可以轻松支持 transactional semantics ,以及缓冲/惰性存储访问(即使是文件)。

对于您的示例实现,SqlConnectionSqlDataReader 将位于您的工作单元类中,映射代码和特定存储过程名称可能位于每个存储库中类。

让这个结构完全独立地工作可能有点棘手,但如果你看一下 Microsoft Entity Framework 生成的代码,它们实际上有它们的工作单元类实例化每个存储库,你只需像访问它一样访问它属性(property)。大致是这样的:

public interface IUnitOfWork : IDisposable
{
void CommitChanges();
void RollbackChanges();
}

public class MyDataModel : IUnitOfWork
{
private bool isDisposed;
private readonly SqlConnection sqlConnection;

public MyDataModel()
{
sqlConnection = DBConnection.GetConnection();
}

// Todo: Implement IUnitOfWork here

public void Dispose()
{
sqlConnection.Dispose();
isDisposed = true;
}

public IRepository<Report> Reports
{
get
{
return new ReportDbRepository(sqlConnection);
}
}
}

public class ReportDbRepository : IRepository<Report>
{
private readonly SqlConnection sqlConnection;

public ReportDbRepository(SqlConnection sqlConnection)
{
this.sqlConnection = sqlConnection;
}

// Todo: Implement IRepository<Report> here using sqlConnection
}

有用的阅读:

关于c# - 对于存储在 ini 文件中的信息,什么是好的 Mapper 模式?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/7640315/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com