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

发布于 2024-08-07 06:36:09 字数 3021 浏览 3 评论 0原文

我们应用程序中的数据访问层将使用 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;

当然一定有比放入如此可怕的黑客更好的方法?毕竟,这些变量根本不会被使用 - ODP.NET 似乎只是需要它们来获取要映射的类型 - 但我认为这可以通过不同的方式来实现。 想法?

The data access layer in our application is going to be using the UDT functionality of Oracle. We will only be passing UDT objects to and from the database.

At the moment, we generate the custom classes using the function provided with ODP.NET (which creates a truly horrible looking class that we really don't want in our codebase).

We then use a separate mapping class, to map the custom class to one of our business objects (and back when saving).

I am trying to find a better way of doing this.

I thought I would just do-away with the generated classes and just write a mapping class that implemented IOracleCustomType. The From/ToCustomObject methods would then map from my UDT to my business objects.
However, this caused me problems when I tried it - I got the error "Object attribute is not mapped to a custom type member".
It appears that as well the two methods, I also need attributes in my mapping class - one attribute for each item in the UDT.

For example - a workflow UDT contains three items - a status, created time and created by.
My UDT is nice and simple:

TYPE workflow_type AS OBJECT
(status                                  VARCHAR2(8)
,created_by                              VARCHAR2(30)
,created_datetime            DATE
);

As is the business object I want it to end up in:

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; }
}

I want to get from one to the other without having to add Oracle code to the business object.

So my thought was to create a mapping class like this:

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;
    }
}

But this doesn't work thanks to the requirement of needing OracleObjectMappingAttribute for each attribute to be mapped (as in the ODP.NET generated classes).
This appears really stupid as I won't be using them at all.
In fact, I can get my mapping class to work, just by adding in three lines:

    [OracleObjectMappingAttribute("STATUS")] public string a;
    [OracleObjectMappingAttribute("CREATED_BY")] public string b;
    [OracleObjectMappingAttribute("CREATED_DATETIME")] public DateTime c;

Surely there must be a better way than putting in such a horrible hack? Afterall, these variables never get used at all - ODP.NET just appears to need them for getting the type to map to - but I would have thought this could be achieved in a different way.
Thoughts?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

若相惜即相离 2024-08-14 06:36:09

这些额外的属性有什么不好呢? .net 类和框架(例如 WCF)中已经存在大量属性。不喜欢属性几乎与不喜欢 .NET 相同。

无论如何,您可以探索 devart 的 Oracle 提供程序的可能性 (http://www. devart.com/dotconnect/oracle/)。他们也有免费版本。它处理 udts 是基于字符串而不是属性。

What is so bad about those extra attributes? There is already a ginormous amount of attributes in the .net classes and frameworks (for instance WCF). Disliking attributes is almost the same as disliking .NET .

Anyway you can explore the possibilities of devart's Oracle provider (http://www.devart.com/dotconnect/oracle/). They have a free version too. Its dealing with udts is based on strings not on attributes.

你好,陌生人 2024-08-14 06:36:09

欢迎来到甲骨文。你会讨厌这里的。你是对的,当你必须显式获取/设置值时,你必须提供这些属性,这绝对是荒谬的。 .NET 生态系统非常通用(特别是在更现代的 .NET Core 中),您完全期望该框架能够处理这样的事情。然而,甲骨文完全错过了这份备忘录(就像他们经常做的那样)。大多数 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 类将迭代所有属性并相应地设置它们。我理解您对向模型添加业务逻辑犹豫不决,但这在很多情况下是不可避免的。

Welcome to Oracle. You'll hate it here. You're right, it's absolutely ridiculous that you'd have to provide those attributes when you have to explicitly get/set the value anyway. The .NET ecosystem is so wonderfully generic (specifically in the more modern .NET Core), that you'd fully expect the framework to handle something like this. However Oracle completely missed that memo (as they often do). Most Oracle frameworks (and the entire Java language in general) has this issue. Here's my workaround:

Start with a base UDT class that implements IOracleCustomType

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));
            }
        }
    }

The above class will look for all public instance properties that have the OracleObjectMappingAttribute attribute applied, and then dynamically get/set there value. Your UDT then becomes just

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; }
}

I LOVE attributes, as it makes it possible to leverage Reflection in order to cut down on repetitive code. With this approach, the BaseUDT class will iterate all properties and set them accordingly. I understand you hesitation to add business logic to your models, but this is unavoidable in a lot of scenarios.

随遇而安 2024-08-14 06:36:09

您可以将 BusinessObject 设置为私有成员并将属性映射到相应的属性。

虽然这不提供任何功能,但至少它们在使用时可以正常运行。

You could set your BusinessObject as a private member and map the attributes to the respective properties.

While this doesn't provide any functionality, at least they would function correctly if used.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文