65.9K
CodeProject 正在变化。 阅读更多。
Home

连接不同数据库的 Facade

starIconstarIconstarIconstarIconstarIcon

5.00/5 (5投票s)

2013年9月19日

CPOL

10分钟阅读

viewsIcon

18676

详细说明如何使用 ORM 模型为多个异构数据库提供通用门面。

介绍 

本文旨在说明如何利用对象关系映射器 (ORM) 模型为不同的数据库提供通用门面,从而使您能够编写一个系统来服务多个(尽管相似)数据库。我将展示一个使用 Entity Framework 的 WCF 服务示例,该服务接口连接两个不同的数据库模式(MSSQL 和 Oracle)。我希望避免编译器指令强制使用某个 ORM 模型;以及条件代码将流程导向重复的部分。

示例数据库都实现了许可证消耗系统。一个 WCF Web 服务将与一个对象上下文接口连接,该对象上下文的概念模型对于 MSSQL 和 Oracle 数据库的对象上下文都是通用的。存储模式和映射模式对于每个不同的数据库都将是唯一的。因此,服务代码可以编写一次,并将在两个数据库中实现其功能。

本文旨在说明如何将单个服务用于两个(或更多)数据库,它不会深入探讨超出示例的技术复杂性。您比任何人都更了解您自己的系统,并且最适合在您自己的实现中处理细节。

背景

原因

有两个不同的产品,一个使用 MSSQL 服务器编写,另一个使用 Oracle 编写,最近由同一个管理团队管理。这些产品各自拥有自己的市场份额和独特的身份。然而,它们有一些共同点,主要兴趣点在于许可证消耗领域,即根据需要发布和消耗使用产品/服务的权利。

Oracle 产品具有明显的数据库偏向,并且是更灵活的设置。这里提供的任何一个模式都不是来自实际系统。

限制

这两个系统都不能进行任何重大修改,因为它们支持现有的(几乎是遗留的)系统,但是可以随意向任何一个系统添加对象(表列)。

使用 .Net 3.5 为 Devart (Oracle) 编写,并为 MSSQL 实例匹配 EDMX 模型,因为我能访问的 Devart 相当旧,只适用于 3.5。Web 服务是用 .Net 4.0 编写的。

现有系统

MSSQL 架构,注意单个 USES 表和单独的 FREE PERIODS 表。

Oracle 架构,注意分区的 USES 表和集成的 FREE PERIODS(在 PERMITS 表中)。

CREATE TABLE PERMITS
  (
   PERMIT_UID                       NUMBER(10) NOT NULL,
   NAME                             VARCHAR2(400),
   LICENSED_PERMITS                 NUMBER(10),
   FREE_PERMITS                     NUMBER(10),
   FREE_WINDOW                      NUMBER(10),
   LICENSEE_UID                     NUMBER(10),
   DATE_CREATED                     DATE DEFAULT SYSDATE NOT NULL, 
   PRIMARY KEY (PERMIT_UID)
  );

COMMENT ON COLUMN PERMITS.FREE_WINDOW IS 'Window of time a free permit is allowed, in days';

CREATE TABLE LICENSED_PERMITS_CONSUMED
  (
   LICENSED_PERMITS_CONSUMED_UID    NUMBER(10) NOT NULL,
   PERMIT_UID                       NUMBER(10) NOT NULL,
   DATE_CONSUMED                    DATE NOT NULL,
   PRIMARY KEY (LICENSED_PERMITS_CONSUMED_UID),
   CONSTRAINT FK_PERMITS
    FOREIGN KEY (PERMIT_UID)
    REFERENCES PERMITS(PERMIT_UID)
  );

CREATE TABLE FREE_PERMITS_CONSUMED
  (
   FREE_PERMITS_CONSUMED_UID        NUMBER(10) NOT NULL,
   LICENSED_PERMITS_CONSUMED_UID    NUMBER(10) NOT NULL,
   DATE_CONSUMED                    DATE NOT NULL,
   PRIMARY KEY (FREE_PERMITS_CONSUMED_UID),
   CONSTRAINT FK_LICENSED_PERMITS
    FOREIGN KEY (LICENSED_PERMITS_CONSUMED_UID)
    REFERENCES LICENSED_PERMITS_CONSUMED(LICENSED_PERMITS_CONSUMED_UID)
  );

实施

门面

最初的计划要求一个概念模式,对两个数据库模式通用,但为每个不同的系统提供唯一的映射和存储模式。

这可以通过多种方式实现,从直接修改 ORM 模型中的映射;到使用视图和存储过程来向其中一个数据库呈现新的形状,从而使其更接近另一个数据库。

本文重点介绍后一种选项——通过模型利用视图和存储过程。

一旦它们具有相似的形状,ORM 概念层就可以用于提供一个对两个系统都通用的门面。请参阅组件图。

首先,我调查了两种模式,以确定哪种最适合转换为最终概念模型。在这种情况下,选择了 Oracle 数据库,主要是因为使用视图转换模式的便利性,以及现有系统使用大量存储过程、视图和其他数据库对象的事实。

数据库工作

计划要求视图将现有 Oracle 模式转换为与 MSSQL 模式匹配的模式,然后 ORM 可以从那里为两个系统提供一个通用门面。然而,并非所有情况下都能进行直接转换,因为会存在问题,主要围绕主键。

如果 Entity Framework 找不到主键,它将从表中所有非空列创建主键,而数据库视图通常缺少主键,但可以将禁用主键添加到 Oracle 视图中,以提供足以满足 Entity Framework 的主键定义。Oracle 不允许在视图上启用主键,因为它们是为了性能而不是数据完整性;请参阅 http://docs.oracle.com/cd/B10500_01/appdev.920/a96590/adg05itg.htm

PERMITS => 许可证

这是最直接的转换,只是加入新的 FreePeriods(见下文)视图,使其稍微复杂一些。

create or replace view LICENSES as
SELECT p.LICENSED_PERMITS as NumberOfLicenses, p.FREE_PERMITS as NumberOfFrees, p.NAME as Name, 
fp.Id  as FreePeriodId, p.PERMIT_UID as Id, p.LICENSEE_UID as LicenseeId
FROM PERMITS p
left join FREEPERIODS fp on p.FREE_WINDOW * (24*60*60) = fp.PERIODLENGTH;

alter view LICENSES
add constraint LICENSES_PK primary key (Id) disable;

PERMITS => FreePeriods

Free Periods 表是从 Permits 表中提取的,并进行了数据转换,将类型从天数更改为秒数。

create or replace view FREEPERIODS as 
select rownum as Id, PeriodLength, Name from
(
 select distinct FREE_WINDOW * 24 * 60 * 60 as PeriodLength, TO_CHAR(FREE_WINDOW) || ' Days' as Name
 from PERMITS
);

alter view FREEPERIODS 
add constraint FREEPERIODS_PK primary key (Id) disable;

然而,这依赖于 Oracle 的 rownum 伪列在视图上提供一种主键,这不是一个真正的主键,也不应被视为如此。请参阅 http://docs.oracle.com/cd/B19306_01/server.102/b14200/pseudocolumns009.htm。在使用这些字段时必须仔细考虑,例如,在显示 Free Periods 列表时,不要依赖此字段对数据进行排序,因为它可能会随着每次查询而改变;或者在插入 Uses 时,一旦插入新记录,rownum(通过视图)就会改变。

一个更优雅的解决方案是向 PERMITS 表添加一个列,该列可以作为 FreePeriods 视图的主键。它将由触发器填充(这样现有的遗留系统就不必改变),并且该数字将来自一个序列(MSSQL 的自动编号)。下面是用于填充现有数据库中字段的光标以及用于填充值的触发器。

ALTER TABLE PERMITS
ADD FREEPERIOD_ID INTEGER;
/

DECLARE
  CURSOR C_FREEPERIOD_ID IS
    SELECT PERMIT_UID
    FROM PERMITS
    WHERE FREEPERIOD_ID IS NULL;
    
  FP_ID INTEGER;  
BEGIN
  FOR PERMIT_REC IN C_FREEPERIOD_ID
  LOOP
    SELECT FREE_PERIOD_ID_SEQ.NextVal
    INTO FP_ID
    FROM dual;
    
    UPDATE PERMITS p
    SET FREEPERIOD_ID = FP_ID
    WHERE p.PERMIT_UID = PERMIT_REC.PERMIT_UID;
  END LOOP;    
  COMMIT;
END;
/

ALTER TABLE PERMITS
MODIFY FREEPERIOD_ID NOT NULL;
/

CREATE OR REPLACE TRIGGER TRIG_PERMITS_FREEPERIOD_ID
  BEFORE INSERT ON PERMITS
  REFERENCING OLD AS OLD NEW AS NEW
  FOR EACH ROW
BEGIN
  --disregard any value inputted.
  SELECT FREE_PERIOD_ID_SEQ.NextVal
    INTO :new.FREEPERIOD_ID
  FROM dual;
END;
/

所以现在 FreePeriod 视图看起来像这样

create or replace view FREEPERIODS as 
select Id, PERIODLENGTH as PeriodLength, NAME from
(
 select distinct FREE_WINDOW * 24 * 60 * 60 as PERIODLENGTH, TO_CHAR(FREE_WINDOW) || ' Days' as NAME,
 FREEPERIOD_ID as Id
 from PERMITS
);
/

alter view FREEPERIODS 
add constraint FREEPERIODS_PK primary key (Id) disable;
/

LICENSED_PERMITS_CONSUMED & FREE_PERMITS_CONSUMED => 使用

这两个独立的表可以使用 Union 语句集成,并通过连接在 FREE 表中提供 PERMIT UID 列。为了提供主键,Oracle 模式中现有的表与 FreePeriods 表一样进行了修改。

由于使用了单独的序列来提供主键的编号,因此可以在任何一个 Oracle 表上生成连续的、不重复的值。

ALTER TABLE LICENSED_PERMITS_CONSUMED
ADD USES_ID INTEGER;
/

DECLARE
  CURSOR C_USES_ID IS
    SELECT LICENSED_PERMITS_CONSUMED_UID
    FROM LICENSED_PERMITS_CONSUMED
    WHERE USES_ID IS NULL;
    
  US_ID INTEGER;  
BEGIN
  FOR LPC IN C_USES_ID
  LOOP
    SELECT USES_ID_SEQ.NextVal
    INTO US_ID
    FROM dual;
    
    UPDATE LICENSED_PERMITS_CONSUMED L
    SET USES_ID = US_ID
    WHERE L.LICENSED_PERMITS_CONSUMED_UID = LPC.LICENSED_PERMITS_CONSUMED_UID;
  END LOOP;    
  COMMIT;
END;
/

ALTER TABLE LICENSED_PERMITS_CONSUMED
MODIFY USES_ID NOT NULL;
/

CREATE OR REPLACE TRIGGER TRIG_LPC_USES_ID
  BEFORE INSERT ON LICENSED_PERMITS_CONSUMED
  REFERENCING OLD AS OLD NEW AS NEW
  FOR EACH ROW
BEGIN
  --disregard any value inputted.
  SELECT USES_ID_SEQ.NextVal
    INTO :new.USES_ID
  FROM dual;
END;
/

ALTER TABLE FREE_PERMITS_CONSUMED
ADD USES_ID INTEGER;
/

DECLARE
  CURSOR C_USES_ID IS
    SELECT FREE_PERMITS_CONSUMED_UID
    FROM FREE_PERMITS_CONSUMED
    WHERE USES_ID IS NULL;
    
  US_ID INTEGER;  
BEGIN
  FOR FPC IN C_USES_ID
  LOOP
    SELECT USES_ID_SEQ.NextVal
    INTO US_ID
    FROM dual;
    
    UPDATE FREE_PERMITS_CONSUMED f
    SET USES_ID = US_ID
    WHERE f.FREE_PERMITS_CONSUMED_UID = FPC.FREE_PERMITS_CONSUMED_UID;
  END LOOP;    
  COMMIT;
END;
/

ALTER TABLE FREE_PERMITS_CONSUMED
MODIFY USES_ID NOT NULL;
/

CREATE OR REPLACE TRIGGER TRIG_FPC_USES_ID
  BEFORE INSERT ON FREE_PERMITS_CONSUMED
  REFERENCING OLD AS OLD NEW AS NEW
  FOR EACH ROW
BEGIN
  --disregard any value inputted.
  SELECT USES_ID_SEQ.NextVal
    INTO :new.USES_ID
  FROM dual;
END;
/

现在所有列都已到位,可以完成最终视图。

create or replace view USES as
select Id, LicenseId, DateTimeOfUse, Licensed from
(
  select PERMIT_UID as LicenseId, DATE_CONSUMED as DateTimeOfUse, 'Y' as LICENSED,
  USES_ID as Id
  from LICENSED_PERMITS_CONSUMED
  union
  select PERMIT_UID as LicenseId, frc.DATE_CONSUMED as DateTimeOfUse, 'N' as LICENSED,
  frc.USES_ID as Id
  from FREE_PERMITS_CONSUMED frc
  join LICENSED_PERMITS_CONSUMED lpc on lpc.licensed_permits_consumed_uid = frc.licensed_permits_consumed_uid
);
/
alter view USES 
add constraint USES_PK primary key (Id) disable;
/

概念模型,以最终形式从任一源数据库中提取

概念模型匹配后,需要处理 CrUD。下面说明了用于持久化、删除和更新 USES 实体的存储过程。

插入到 Uses 表中

相当直接,查询许可参数以确定数据需要插入到哪个表中。请记住,USES_ID 列将由 LICENSED_CONSUMED 和 FREE_CONSUMED 表中的触发器填充。

PROCEDURE ADD_TO_USE(v_license_id IN INTEGER, 
                     v_licensed IN CHAR, 
                     v_datetime_of_use IN DATE)
IS    
  v_dummy INTEGER;                
begin
  if (v_licensed = 'Y') then
     v_dummy := ADD_TO_LICENSED_CONSUMED(v_license_id, v_datetime_of_use);  
  else
     v_dummy := ADD_TO_FREE_CONSUMED(v_license_id, v_datetime_of_use);
  end if;
end ADD_TO_USE;

FUNCTION ADD_TO_LICENSED_CONSUMED(v_license_id IN integer, 
                                  v_datetime_of_use IN DATE) 
  return integer is
  v_uid integer;
begin
  select LICENSED_PERMITS_CONSUMED_SEQ.NEXTVAL
   into v_uid
  from dual;
  
  INSERT INTO LICENSED_PERMITS_CONSUMED(LICENSED_PERMITS_CONSUMED_UID, PERMIT_UID, DATE_CONSUMED)
  VALUES (v_uid, v_license_id, v_datetime_of_use); 
  
  return v_uid;
end ADD_TO_LICENSED_CONSUMED;

FUNCTION ADD_TO_FREE_CONSUMED(v_license_id IN integer, v_datetime_of_use IN DATE)
  return integer is
  v_uid integer;
  v_license_consumed_uid integer;
  v_period_length NUMBER;
begin  
   select FREE_PERMITS_CONSUMED_SEQ.NEXTVAL
   into v_uid
   from dual;
  
  SELECT p.free_window
  INTO v_period_length 
  FROM PERMITS p
  WHERE p.Permit_Uid = v_license_id;
  
  SELECT MAX(LICENSED_PERMITS_CONSUMED_UID)
  INTO v_license_consumed_uid
  FROM LICENSED_PERMITS_CONSUMED
  WHERE PERMIT_UID = v_license_id
  AND DATE_CONSUMED >= (v_datetime_of_use - v_period_length)
  ORDER BY DATE_CONSUMED;
  
  INSERT INTO FREE_PERMITS_CONSUMED (FREE_PERMITS_CONSUMED_UID, LICENSED_PERMITS_CONSUMED_UID, DATE_CONSUMED)
  VALUES (v_uid, v_license_consumed_uid, v_datetime_of_use); 
  
  return v_uid;
end ADD_TO_FREE_CONSUMED;

更新 Uses 表

同样,查询许可参数,然后更新相关表。由于 USES_ID 值被传递,因此更新任一构成表都很简单。

PROCEDURE UPDATE_USE(v_use_id IN INTEGER, v_license_id IN INTEGER, v_datetime_of_use IN DATE, v_licensed IN CHAR)
  IS
BEGIN
  IF (v_licensed = 'Y') THEN
    UPDATE_LICENSED_CONSUMED(v_use_id, v_license_id, v_datetime_of_use);
  ELSE
    UPDATE_FREE_CONSUMED(v_use_id, v_datetime_of_use);
  END IF;
END UPDATE_USE;

PROCEDURE UPDATE_LICENSED_CONSUMED(v_use_id IN NUMBER, v_license_id IN INTEGER, v_datetime_of_use IN DATE)
  IS
BEGIN
  UPDATE LICENSED_PERMITS_CONSUMED l
  SET l.permit_uid = v_license_id,
      l.date_consumed = v_datetime_of_use
  WHERE l.uses_id = v_use_id;
END UPDATE_LICENSED_CONSUMED;

PROCEDURE UPDATE_FREE_CONSUMED(v_use_id IN NUMBER, v_datetime_of_use IN DATE)
  IS
BEGIN
  UPDATE FREE_PERMITS_CONSUMED f
  SET f.date_consumed = v_datetime_of_use
  WHERE f.uses_id = v_use_id; 
END UPDATE_FREE_CONSUMED;

从 Uses 表中删除。

PROCEDURE DELETE_USE(v_use_id IN INTEGER, v_license_id IN INTEGER, v_licensed IN CHAR)
  IS
BEGIN
  IF (UPPER(v_licensed) = 'Y') THEN
    DELETE_LICENSED_CONSUMED(v_use_id, v_license_id);
  ELSE
    DELETE_FREE_CONSUMED(v_use_id);
  END IF;
END DELETE_USE;

PROCEDURE DELETE_LICENSED_CONSUMED(v_use_id IN NUMBER, v_license_id IN INTEGER)
  IS
BEGIN
  DELETE FROM LICENSED_PERMITS_CONSUMED l
  WHERE l.uses_id = v_use_id
  AND l.permit_uid = v_license_id;
END DELETE_LICENSED_CONSUMED;

PROCEDURE DELETE_FREE_CONSUMED(v_use_id IN NUMBER)
  IS
BEGIN
  DELETE FROM FREE_PERMITS_CONSUMED fr
  WHERE fr.uses_id = v_use_id;
END DELETE_FREE_CONSUMED;

以上代码是集成过程的核心。每个需要转换的表都需要进行评估并确定最佳方法。大多数数据类型都可以在不丢失数据的情况下进行映射。请参阅 http://docs.oracle.com/cd/E10405_01/doc/appdev.120/e10379/ss_oracle_compared.htm 以获得很好的参考。

如果可以对受限较多的数据库进行更改,即加宽列,那么应该不会有任何真正的障碍阻碍两个系统在可接受的范围内融合。然而,如果数据库一开始就不够接近,则不太可能实现完全的数据保真度。

本文前面展示的视图说明了如何将表连接在一起或将表的某个部分分离出来,以帮助使两个模式对齐。函数和存储过程可以用于帮助截断或存储截断的数据到其他表中,以帮助防止数据丢失。存储过程说明了可以使用过程将数据从服务(门面匹配)转换为数据库中所需的最终形式。

两个数据库引擎还提供“instead of”触发器,可以替代将上述存储过程映射到 ORM 模型。这些触发器允许您将数据插入到视图的组件表中(感谢 Mark)。

性能

在用户和数据之间添加额外层会带来性能损失,但在本例中,这种损失并不太严重,并且收益远远大于负面影响。

实体框架模型

edmx 模型(来自 MSSQL 数据库的 ADO .Net 实体数据模型)和 edml 模型(来自 Devart 的 Oracle 数据库的 dotConnect)都命名为“LicenseDataModel”,以确保所有模型文件具有相同的名称,并且实际上可以互换。

下面显示了 Oracle 对象上下文的映射模式(msl XML 文件的一部分),这说明了存储过程的使用如何帮助实现门面功能。

    <EntitySetMapping Name="UsesSet">
      <EntityTypeMapping TypeName="LicenseModel.Uses">
        <MappingFragment StoreEntitySet="USES">
          <ScalarProperty Name="Id" ColumnName="ID" />
          <ScalarProperty Name="LicenseId" ColumnName="LICENSEID" />
          <ScalarProperty Name="DateTimeOfUse" ColumnName="DATETIMEOFUSE" />
          <ScalarProperty Name="Licensed" ColumnName="LICENSED" />
        </MappingFragment>
      </EntityTypeMapping>
      <EntityTypeMapping TypeName="LicenseModel.Uses">
        <ModificationFunctionMapping>
          <InsertFunction FunctionName="LicenseModel.Store.ADD_TO_USE">
            <ScalarProperty Name="LicenseId" ParameterName="V_LICENSE_ID" />
            <ScalarProperty Name="Licensed" ParameterName="V_LICENSED" />
            <ScalarProperty Name="DateTimeOfUse" ParameterName="V_DATETIME_OF_USE" />
          </InsertFunction>
          <UpdateFunction FunctionName="LicenseModel.Store.UPDATE_USE">
            <ScalarProperty Name="Id" ParameterName="V_USE_ID" Version="Current" />
            <ScalarProperty Name="LicenseId" ParameterName="V_LICENSE_ID" Version="Current" />
            <ScalarProperty Name="DateTimeOfUse" ParameterName="V_DATETIME_OF_USE" Version="Current" />
            <ScalarProperty Name="Licensed" ParameterName="V_LICENSED" Version="Current" />
          </UpdateFunction>
          <DeleteFunction FunctionName="LicenseModel.Store.DELETE_USE">
            <ScalarProperty Name="Id" ParameterName="V_USE_ID" />
            <ScalarProperty Name="LicenseId" ParameterName="V_LICENSE_ID" />
            <ScalarProperty Name="Licensed" ParameterName="V_LICENSED" />
          </DeleteFunction>
        </ModificationFunctionMapping>
      </EntityTypeMapping>
    </EntitySetMapping>

这可以与下面显示的 MSSQL 模型的 MSL 文件进行对比。请注意缺少 ModificationFunctionMapping 标签。

    <EntitySetMapping Name="UsesSet">
      <EntityTypeMapping TypeName="IsTypeOf(MSSQLDataModel.Uses)">
        <MappingFragment StoreEntitySet="Uses">
          <ScalarProperty Name="Id" ColumnName="Id" />
          <ScalarProperty Name="DateTimeOfUse" ColumnName="DateTimeOfUse" />
          <ScalarProperty Name="Licensed" ColumnName="Licensed" />
        </MappingFragment>
      </EntityTypeMapping>
    </EntitySetMapping>

以上两个代码示例展示了使用 ORM 模型为系统提供最终抽象门面的真正优势。您总是可以在不使用 ORM 的情况下使用视图和存储过程做同样的事情——但 ORM 提供了一个非常坚实的抽象,以有效的方式将所有内容整齐地联系在一起。

现在我有一个概念模型,它可以直接通过表和列与 MSSQL 数据库接口,或者间接通过 Oracle DB 中的视图和存储过程接口。

WCF Web 服务

许可证的实际实施和这些许可证使用情况的记录是在一个简单的 WCF 服务中完成的。该服务使用通用的概念模型。请参阅以下类图。

参考文献

如果 ORM 模型呈现的门面是相同的,则可以在任一 ORM 的程序集中定义相同的类,然后 WCF 服务中只需引用其中一个程序集。引用模型后,服务就可以使用实体来实现所需的业务逻辑。为了尽可能简单,我将程序集命名为它们链接到的数据库。请参阅下面的解决方案资源管理器。

但是,如果 ORM 模型嵌入在程序集中,则没有机会在不重新编译的情况下将服务指向其他数据库。

元数据工件处理

为了方便更改目标数据库而无需重新编译服务,数据模型项目应将“元数据工件处理”标志设置为分拆组件文件。然后可以将这些文件添加到 WCF 服务中,以便使用它们,并且它们具有相同的名称。

当对象上下文需要指向不同的数据库时,可以通过将它们替换在 bin 文件夹(或它们所在的位置)中的文件来更改文件,替换为构成其他 ORM 模型的文件。一旦正确的文件到位并且配置文件已更改以反映新的数据库连接字符串(或者您选择的任何方式),服务将毫无疑问地查找新的数据库,前提是已完成所需的设置(Oracle 客户端等)。

这样做的结果是,主 WCF 服务程序集内部的实体和对象上下文的局部类丢失。您可以在每个数据模型程序集或类似的地方编写它们,但我选择在主 WCF 服务程序集中使用扩展方法(以及 C# 5+ 中的扩展属性)或实用工具设计(反)模式一次且仅一次编写代码。

扩展方法

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using LicenseModel;
using UnifiedService.Extensions;

namespace UnifiedService.EntityExtensions
{
    public static class LicenseExtensions
    {
        public static bool IsFreePeriodUse(this License license, LicenseEntities context, DateTime proposedDateTime)
        {
            var freePeriod = context.FreePeriodSet.Where(fp => fp.Id == license.FreePeriodId).First();

            if (freePeriod != null)
            {
                var dateTimeOfLastLicensedUse = context.UsesSet.Where(u => (u.Licensed == "Y")
                                                                        && (u.LicenseId == license.Id))
                                                                        .Max(u => u.DateTimeOfUse).GetValueOrDefault();
                double periodLength = freePeriod.PeriodLength.HasValue ? freePeriod.PeriodLength.Value : 0;
                var freeWindow = dateTimeOfLastLicensedUse.AddSeconds(periodLength);

                if (proposedDateTime.IsBetween(dateTimeOfLastLicensedUse, freeWindow))
                {
                    var numberOfFreesAfter = context.UsesSet.Where(u => (u.Licensed == "N")
                                                                    && (u.LicenseId == license.Id)
                                                                    && (u.DateTimeOfUse > dateTimeOfLastLicensedUse)
                                                                    && (u.DateTimeOfUse <= freeWindow)).Count();
                    if (numberOfFreesAfter < license.NumberOfFrees)
                    {
                        return true;
                    }

                }
            }
            return false;
        }
    }
}

实用程序

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using LicenseModel;

namespace UnifiedService.EntityUtilities
{
    public static class UsesUtility
    {
        public static Uses CreateAndPopulateNewFreeUse(DateTime dateTime, LicenseEntities context, License license)
        {
            Uses uses = new Uses();

            uses.DateTimeOfUse = dateTime;
            uses.Licensed = "N";
            uses.LicenseId = license.Id;
            context.AddToUsesSet(uses);
            context.SaveChanges();

            return uses;
        }

        public static Uses CreateAndPopulateNewLicensedUse(DateTime dateTime, LicenseEntities context, License license)
        {
            Uses uses = new Uses();

            uses.DateTimeOfUse = dateTime;
            uses.Licensed = "Y";
            uses.LicenseId = license.Id;
            context.AddToUsesSet(uses);
            context.SaveChanges();

            return uses;
        }
    }
}

统一服务

获取许可证

非常简单的许可证消耗示例——如果此使用可以是免费期使用,则使其成为免费期使用,否则确定它是否是有效的许可使用。

        public AddAUseResult AddAUse(DateTime dateTime, int licenseId)
        {
            AddAUseResult result = null;
            try
            {
                var context = new LicenseEntities(ConnectionsUtility.ReturnMetadataAndConnectionString());

                var license = context.LicenseSet.Where(l => l.Id == licenseId).First();

                var numberOfLicensesUsed = context.UsesSet.Where(u => (u.Licensed == "Y") && (u.LicenseId == license.Id)).Count();

                if ((numberOfLicensesUsed > 0) && (license.NumberOfFrees > 0))
                {
                    var freePeriod = license.IsFreePeriodUse(context, dateTime);

                    if (freePeriod)
                    {
                        var uses = UsesUtility.CreateAndPopulateNewFreeUse(dateTime, context, license);

                        result = AddAUseResult.CreateSuccessfulResult(uses.Id);
                    }
                }

                if ((result == null) && (numberOfLicensesUsed < license.NumberOfLicenses))
                {
                    var uses = UsesUtility.CreateAndPopulateNewLicensedUse(dateTime, context, license);

                    result = AddAUseResult.CreateSuccessfulResult(uses.Id);
                }

                if (result == null)
                {
                    result = AddAUseResult.CreateFailedResult();
                }

                return result;
            }
            catch (Exception exception)
            {
                IntegratedFaultContracts fault = new IntegratedFaultContracts
                {
                    MethodName = "AddAUse",
                    Problem = "Exception",
                    Exception = exception.ToString()
                };
                throw new FaultException<integratedfaultcontracts>(fault, new FaultReason(fault.ToString()));
            }
        }

更新使用

由于我们直接提供了使用的 ID,因此根据需要更改时间是一个简单的过程。

        public bool UpdateAUse(DateTime dateTime, int useId)
        {
            try
            {
                var context = new LicenseEntities(ConnectionsUtility.ReturnMetadataAndConnectionString());

                var use = context.UsesSet.Where(u => u.Id == useId).FirstOrDefault();

                use.DateTimeOfUse = dateTime;

                var saves = context.SaveChanges();

                return (saves > 0);
            }
            catch (Exception exception)
            {
                IntegratedFaultContracts fault = new IntegratedFaultContracts
                {
                    MethodName = "UpdateAUse",
                    Problem = "Exception",
                    Exception = exception.ToString()
                };
                throw new FaultException%lt;integratedfaultcontracts>(fault, new FaultReason(fault.ToString()));
            }
        }

删除使用

同样,使用主键(Id)使删除成为一个简单的过程。

        public bool DeleteAUse(int useId)
        {
            try
            {
                var context = new LicenseEntities(ConnectionsUtility.ReturnMetadataAndConnectionString());

                var use = context.UsesSet.Where(u => u.Id == useId).FirstOrDefault();

                context.DeleteObject(use);

                var saves = context.SaveChanges();

                return (saves > 0);
            }
            catch (Exception exception)
            {
                IntegratedFaultContracts fault = new IntegratedFaultContracts
                {
                    MethodName = "DeleteAUse",
                    Problem = "Exception",
                    Exception = exception.ToString()
                };
                throw new FaultException<integratedfaultcontracts>(fault, new FaultReason(fault.ToString()));
            }
        }

结论

通过操作对象上下文的存储和映射模式,可以实现一个系统,该系统可以与多个数据库接口,而无需更改构成该系统的业务逻辑。您真正需要的只是两个需要相同功能且具有合理相似数据库模式的现有系统(可以通过使用视图和其他数据库对象使它们更接近)。

这是我在 CodeProject 上的第一篇文章,非常感谢任何反馈。

© . All rights reserved.