如何在不使用OracleObjectMappingAttribute的情况下,使用ODP.NET从Oracle UDTs中进行映射?

9
我们应用程序中的数据访问层将使用Oracle的UDT功能。我们只会向数据库传递UDT对象。
目前,我们使用ODP.NET提供的函数来生成自定义类(它会创建一个非常难看的类,我们不想在代码库中使用)。
然后,我们使用单独的映射类将自定义类映射到我们的业务对象之一(并在保存时进行反向映射)。
我正在试图找到更好的方法来做这件事。
我认为我可以放弃生成的类,只编写一个实现IOracleCustomType的映射类。 From/ToCustomObject 方法将从我的UDT映射到我的业务对象。 然而,当我尝试这样做时遇到了问题——我得到了错误消息“对象属性未映射到自定义类型成员”。 似乎除了这两个方法之外,我的映射类还需要属性——UDT中每个项需要一个属性。
例如-工作流UDT包含三个项-状态、创建时间和创建者。 我的UDT很简单:
TYPE workflow_type AS OBJECT
(status                                  VARCHAR2(8)
,created_by                              VARCHAR2(30)
,created_datetime            DATE
);

作为我想要的业务对象:

public class Workflow
{
    /// <summary>
    /// Gets the status of the workflow.
    /// </summary>
    /// <value>The status.</value>
    public string Status { get; private set; }

    /// <summary>
    /// Gets the Windows Logon Id of the user performing the action
    /// </summary>
    public string CreatedBy{ get; private set; }
    /// <summary>
    /// Gets the time of the action 
    /// </summary>
    public DateTime CreatedTime { get; private set; }
}

我希望能够在不增加Oracle代码到业务对象的情况下完成从一个对象到另一个对象的转换。
因此,我的想法是创建一个类似这样的映射类:
```` // 代码示例 ````
public class WorkFlowMapper : IOracleCustomType
{
    public BusinessObjects.WorkFlow BusinessObject {get; private set;}

    public WorkFlowMapper(BusinessObjects.WorkFlow businessObject)
    {
        BusinessObject = businessObject;
    }

    public WorkFlowMapper(){}

    public void FromCustomObject(OracleConnection con, IntPtr pUdt)
    {
        OracleUdt.SetValue(con, pUdt, "STATUS", BusinessObject.Status);
        OracleUdt.SetValue(con, pUdt, "CREATED_BY", BusinessObject.CreatedBy);
        OracleUdt.SetValue(con, pUdt, "CREATED_DATETIME", BusinessObject.CreatedTime);
    }

    public void ToCustomObject(OracleConnection con, IntPtr pUdt)
    {

        BusinessObject = new BusinessObjects.WorkFlow(
            (string)OracleUdt.GetValue(con, pUdt, "STATUS"),
            (string)OracleUdt.GetValue(con, pUdt, "CREATED_BY"),
            (string)OracleUdt.GetValue(con, pUdt, "CREATED_DATETIME")
        );
    }
}

// Factory to create an object for the above class
[OracleCustomTypeMappingAttribute("MYUSER.WORKFLOW_TYPE")]
public class CurrencyExposureFactory : IOracleCustomTypeFactory
{

    public virtual IOracleCustomType CreateObject()
    {
        WorkFlowMapper obj = new WorkFlowMapper();
        return obj;
    }
}

但是由于需要为每个要映射的属性添加OracleObjectMappingAttribute(在ODP.NET生成的类中),所以这种方法行不通。这看起来非常愚蠢,因为我根本就不会使用它们。事实上,只需添加三行代码即可使我的映射类正常工作:
    [OracleObjectMappingAttribute("STATUS")] public string a;
    [OracleObjectMappingAttribute("CREATED_BY")] public string b;
    [OracleObjectMappingAttribute("CREATED_DATETIME")] public DateTime c;

肯定有比使用这样可怕的hack更好的方法吧?毕竟,这些变量根本没有被使用 - ODP.NET似乎只需要它们来获取映射类型 - 但我认为可以用不同的方式实现这一点。你有什么想法吗?

3个回答

2
这段文字的意思是:“那些额外的属性有什么不好的呢?.net类和框架(例如WCF)中已经有大量的属性了。不喜欢属性几乎就等于不喜欢.NET。无论如何,您可以探索devart的Oracle提供程序(http://www.devart.com/dotconnect/oracle/)的可能性。他们也有免费版本。它处理udt是基于字符串而不是属性的。”

4
不好的是它们不应该被需要。这些属性本身从未被获取或设置,唯一需要它们的原因是每个用户定义类型(UDT)属性似乎都需要一个OracleObjectMappingAttribute。在一个类中有许多未被使用的公共属性看起来很疯狂(是的,它们也必须是public!)。因此,我想质疑的是公共属性的需求,而不是属性本身。 - David
哦,谢谢您提供的另一个Oracle提供程序链接(不幸的是,对于我个人来说,这并不会有所帮助,因为在我工作的地方,只使用ODP.NET)。 - David
代码中确实存在一些“重复”,因为你必须在代码中进行映射并使用属性进行映射,但我认为这不是一个大问题。虽然它是一个问题,但不是一个很大的问题。 - Theo
似乎很疯狂,因为带有属性的属性实际上从未用于保存数据。它们只是坐在那里允许映射属性存在。<br> 这听起来不太好,对吧?<br>我已经在Oracle论坛上发布了关于此事的帖子 - 我相信一定有更好的方法来解决这个问题: http://forums.oracle.com/forums/thread.jspa?threadID=965904&tstart=0 - David
如果您完全遵循单一职责原则,那么您必须为数据访问和业务逻辑分别创建不同的类。这意味着您必须开发映射方法来连接数据访问类和业务逻辑类。如果您在数据访问对象和业务逻辑类之间使用映射方法,则无需在业务逻辑类上具有Oracle属性。您想要一个快捷方式,而不是为数据访问创建独立的单独类。 - tuinstoel
所以你的意思是数据访问层应该返回包含这些属性的对象(这将是我上面示例中的映射类),然后在业务层中,我们应该从这些对象转换为我们的业务对象?个人认为,在责任分离方面,这有点极端。个人很高兴只将数据访问部分保留在数据访问层中。我将业务对象保存在与业务层分开的程序集中 - 这样它们可以在层之间轻松传递。也许它们可以更好地命名为“实体”。 - David

2
欢迎来到Oracle。你会讨厌这里的。你说得对,当你必须明确获取/设置值时,你不得不提供那些属性,这绝对是荒谬的。.NET生态系统非常通用(特别是在更现代的.NET Core中),你完全希望框架处理这样的问题。然而,Oracle完全错过了那个备忘录(他们经常这样)。大多数Oracle框架(以及整个Java语言)都有这个问题。这是我的解决方法:
从实现IOracleCustomType的基本UDT类开始。
public abstract class BaseUDT : IOracleCustomType
    {
        private IEnumerable<PropertyInfo> GetTypeProperties() => GetType()
                .GetProperties(BindingFlags.Public | BindingFlags.Instance)
                .Where(p => p.GetCustomAttribute<OracleObjectMappingAttribute>() != null);

        public virtual void ToCustomObject(OracleConnection con, IntPtr pUdt)
        {
            foreach (var prop in GetTypeProperties())
            {
                var attr = prop.GetCustomAttribute<OracleObjectMappingAttribute>();
                prop.SetValue(this, OracleUdt.GetValue(con, pUdt, attr.AttributeName));
            }
        }

        public virtual void FromCustomObject(OracleConnection con, IntPtr pUdt)
        {
            foreach (var prop in GetTypeProperties())
            {
                var attr = prop.GetCustomAttribute<OracleObjectMappingAttribute>();
                OracleUdt.SetValue(con, pUdt, attr.AttributeName, prop.GetValue(this));
            }
        }
    }

上述类将查找所有已应用OracleObjectMappingAttribute属性的公共实例属性,然后动态获取/设置其值。您的UDT就变成了这个样子。
public class Workflow : BaseUDT
{
    /// <summary>
    /// Gets the status of the workflow.
    /// </summary>
    /// <value>The status.</value>
    [OracleObjectMapping("STATUS")]
    public string Status { get; private set; }

    /// <summary>
    /// Gets the Windows Logon Id of the user performing the action
    /// </summary>
    [OracleObjectMapping("CREATED_BY")]
    public string CreatedBy{ get; private set; }
    /// <summary>
    /// Gets the time of the action 
    /// </summary>
    [OracleObjectMapping("CREATED_DATETIME")]
    public DateTime CreatedTime { get; private set; }
}


我喜欢属性,因为它使得利用反射来减少重复代码成为可能。采用这种方法,BaseUDT类将迭代所有属性并相应地设置它们。我理解你不愿向模型添加业务逻辑,但在许多情况下这是不可避免的。

可以使用 Oracle.ManagedDataAccess.Client 完成吗? - Spongman
我会这样假设!我自己还没有尝试过。 - thomasrea0113

0
你可以将你的BusinessObject设置为私有成员,并将属性映射到相应的属性上。
虽然这并不提供任何功能,但至少如果使用它们,它们会正确地发挥作用。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接