.NET 學習

.NET 學習生活感想... 万事成蹉跎..... 贵在坚持 及时整理自己做过和学过的东西

博客园 首页 新随笔 联系 订阅 管理

原文地址:http://community.devexpress.com/blogs/eaf/archive/2008/10/09/xcrm-activities.aspx

XCRM: Activities

This post is entirely devoted to Activities. It's more interesting to design Activities, because they are more complex than other components that I described earlier. There are three points I consider when designing Activities:
这篇完全投入到Activites。设计Activities更有兴趣,因为它比我以为说些的组件更复杂。我设计Activities有三点要考虑:

  1. There can be many Activity types: a phone call, email, meeting/appointment, task, campaign activity or case/service activity.
    活动有多种类型:一个电话,邮件,会议/约会,任务,营销活动或者个别服务活动。
  2. An Activity has an owner - a person who is assigned to perform the Activity. The person should be able to see all his/her activities in one list.
    一个活动应有一个执行者。这个执行者能在列表看到所有活动。
  3. An Activity can be related to a Contact, Account, Lead, Opportunity, Marketing Campaign or any other “main” entity record in the system.
    一个活动能关系到一个联系人,客户,线索,机会,市场营销或系统中任何其他“主”实体记录

Let's go through all these points:
让我们思考所有这些点:

1. We can divide all Activities into two basic categories:
 
我们可以将所有活动分成两种基本的类别:

  • Event
    事件

An Activity that has clear start and end points. Generally, Events can be scheduled on a calendar. So, the system should be able to show all Events in one Calendar view.
一个活动应有启动和结束。通常,事件能安排在日历上。因此,这个系统能在一个日历视图中显示所有事件。

  • Task
    任务

A less time-bound Activity than the Event. It may have a Due Date only.
活动比事件不太有时间限制。它可能只有一个截止日期。

Simple CRM systems work with Tasks and Events simultaneously. More complex systems distinguish these Activity types, so they can track specialized information, like a phone number for a call.
简单的CRM系统任务和事件可以同时工作。更复杂的系统区分这些活动类型,因此,它们能跟踪到更具体的信息,像呼叫的电话号码。

In our application, let’s define a Task and Event. In addition, let's create specialized Activities: a Phone Call, as a variation of the Task Activity, and an Appointment, as a variation of the Event Activity. Here are the tests that express these requirements:
在我们的应有程序中,让我们定义一个任务和事件。另外,让我们再创建一个专门的活动:一个电话,作为一个任务活动,一个销售机会,作为事件活动。

[Test]
public void ActivityTypesTest() {
    ICall call = ObjectSpace.CreateObject<ICall>();
    IAppointment appointment = ObjectSpace.CreateObject<IAppointment>();
    ITask task = ObjectSpace.CreateObject<ITask>();
    IEvent eventObj = ObjectSpace.CreateObject<IEvent>();
 
    IList<IActivity> allActivities = ObjectSpace.GetObjects<IActivity>(null);
    Assert.AreEqual(4, allActivities.Count);
    IList<IEvent> allEvents = ObjectSpace.GetObjects<IEvent>(null);
    Assert.AreEqual(2, allEvents.Count);
}
[DomainComponent]
public interface IActivity {
    string Subject { get; set; }
    
    IActivityTarget RelatedTo { get; set; }
}
[DomainComponent]
public interface IEvent : IActivity {
    DateTime StartTime { get; set; }
    DateTime EndTime { get; set; }
    bool AllDayEvent { get; set; }
}

public enum TaskPriority { Low, Medium, High }
 
[DomainComponent]
public interface ITask : IActivity {
    TaskPriority Priority { get; set; }
    DateTime DueDate { get; set; }
}
[DomainComponent]
public interface ICall : ITask {
}
[DomainComponent]
public interface IAppointment : IEvent {
}

I don’t call the RegisterDC method in this test, because I've moved all Activity registrations to the SetUp method (it is called before a test is started by NUnit):
在这个测试中,我不调用RegisterDC方法。因为我移动所有活动注册到SetUp方法(Nunit开始一个测试前调用它)

[SetUp]
public override void SetUp() {
    base.SetUp();
    RegisterDC<ILead>();
    RegisterDC<ICampaign>();
    RegisterDC<IContact>();
    RegisterDC<IAccount>();
    RegisterDC<IOpportunity>();
    RegisterDC<IEvent>();
    RegisterDC<IAppointment>();
    RegisterDC<ITask>();
    RegisterDC<ICall>();
    Generate();
}

Note that I don't register the IActivity. This is a base abstract interface that cannot be instantiated in our system.
注意:我没有注册这个活动。这是一个基抽象接口,在我们的系统中不能被实例化。

2. An Activity has an owner – usually the system's user. The owner may have a list of Activities:
 
一个活动有一个所有者 - 通常是系统用户。所有者可能有一个活动列表:

[DomainComponent]
public interface ITestUser : IActivityOwner {
}
[TestFixture]
public class ActivityTests : BaseTest {
    
    [Test]
    public void ActivityOwnershipTest() {
        ITask blogAboutDC = ObjectSpace.CreateObject<ITask>();
        ITestUser romanEremin = ObjectSpace.CreateObject<ITestUser>();
        blogAboutDC.Owner = romanEremin;
        Assert.AreEqual(blogAboutDC, romanEremin.Activities[0]);
    }
}
[DomainComponent]
public interface IActivityOwner {
    IList<IActivity> Activities { get; }
}
[DomainComponent]
public interface IActivity {
    
    IActivityOwner Owner { get; set; }
}

3. An Activity can be related to a Contact, Account and any other “main” record in the system. Some systems allow you to link Activities to several records of different type, but I guess this is just a workaround to make programming easier. Our DC framework should be flexible, so I will design it in “the right way”:
 
一个活动能关联一个联系人,客户,其它系统中任何“主”记录。有些系统允许你给活动链接几个不同类型的记录,但,我猜想这可能是中变通方法,使编程更容易。我们的DC框架应当是比较灵活的,因此,我们将以正常的方式设计它:

[Test]
public void ActivityRelationTest() {
    IAccount account = ObjectSpace.CreateObject<IAccount>();
    IContact contact = ObjectSpace.CreateObject<IContact>();
    ILead lead = ObjectSpace.CreateObject<ILead>();
    IOpportunity opportunity = ObjectSpace.CreateObject<IOpportunity>();
    ITask task = ObjectSpace.CreateObject<ITask>();
 
    task.RelatedTo = account as IActivityTarget;
    Assert.IsNotNull(account.Activities);
    Assert.AreEqual(task, account.Activities[0]);
    task.RelatedTo = contact as IActivityTarget;
    Assert.AreEqual(task, contact.Activities[0]);
    task.RelatedTo = lead as IActivityTarget;
    Assert.AreEqual(task, lead.Activities[0]);
    task.RelatedTo = opportunity as IActivityTarget;
    Assert.AreEqual(task, opportunity.Activities[0]);
}

To make this test pass, I should define the IActivityTarget interface. Only the components that implement this interface will be able to be added to the “related to” list:
要使这个测试通过,我应当定义IactivityTarget接口。仅当这个实现接口的组件,才能被添加到“相关“列表:

[DomainComponent]
public interface IActivityTarget {
    IList<IActivity> Activities { get; }
}

[DomainComponent]
public interface IActivity {
    
    IActivityTarget RelatedTo { get; set; }
}

Note that I have not declared a requirement that I want to be able to see all Activity Targets in one list. In that instance, we would have to create a common table for them somehow, and this might be a major restriction on possible use cases of Activities.
我没有定义在一个列表能看到所有活动目标的需求。在该实例,我们不得不以每一种方法建立一个公用表,这可能是一个对各种状态的限制。

I don’t require all IActivityTarget implementers to be in one list, but I still need to be able to edit the RelatedTo field in a UI. This editor should let end-users locate an object of one of predefined types. CRM systems tend to achieve this by adding a combo box with object types. In this instance, end-users first select the type, and then the object of this type:
我需要在一个列表中实现所有IactivityTarget,但仍需能在界面编辑相关字段。这个编辑应当能让终端用户找出一个预定义类型。CRM系统往往通过添加一个对象类型组合框实现。在这个实例中,终端用户首先选择一个类型,然后选择这个类型对象:

As far as I know,  we don’t have an editor of this kind yet. So, I've written down a task to implement this editor and added a test that describes this scenario. To tell you the truth, the XpoBuilder has trouble with this test. Let me explain in more detail.
据我所知,我们仍然没有一个这种类型的编辑。因此,我写下一个实现这个编辑的任务,添加了一个描述这种方法的测试。要工时你真相,XpoBuilder用这个测试麻烦。需要我们更多的详细的解释。

How to persist a reference to the interface that is implemented in several classes.(如何持久化一个在几个类中实现的接口引用)

Here, we have a case of leaky abstraction, impedance mismatch and that sort of thing. The fact is that a relational database is not mapped to the OO world easily. In our case – how would you manually implement the scenario where an Activity can be related to one of several records, say, Account or Contact? I see two ways (note that we exclude the requirement that the Contact and Account must have the same base).
这里,我们有一种抽象泄露的情况,阻抗失配诸如此类的事情。事实上,一个更新数据库没有映射到面向对象世界容易。在我们的案例中 如何手动实现方法,一个活动能关联到一个或多个记录,也就是说,客户或联系人?我看到两种方法(注意:我们排除,联系人和客户必须有相同的基础)

The first approach - Multiple associations.(第一个方法 多关联)

Make two associations:(使两个关联)

The implementation of the IActivity.RelatedTo property will look like this:
IActivity.RelatedTo
属性实现,像这样:

public IActivityTarget RelatedTo {
    get {
        if (Contact != null) return Contact;
        if (Account != null) return Account;
        return null;
    }
    set {
        Contact = null;
        if (value is Contact) {
            Contact = value as Contact;
            Account = null;
            return;
        }
        if (value is Account) {
            Contact = null;
            Account = value as Account;
            return;
        }
    }
}

This code is ugly, but other than that it looks fine – the Contact and Account have a list of Activities. An Activity allows you to access a Contact or Account as IActivityTarget. But let’s make a simple change. Let's add the Active property to the IActivityTarget interface:
这个代码看起来有点丑陋,但比其它代码看起来好看些 联系人和客户有一个活动列表。一个活动允许你访问一个联系人或客户作为IactivityTarget.但,我我们做一个简单的更改。让我们给IActivityTarget接口添加一个Active属性:

public interface IActivityTarget {
    IList<IActivity> Activities { get; }
    bool Active { get; set; }
}

XPO will not be able to apply a criteria “[RelatedTo.Active] = true” to Activities on the server, because the RelatedTo is not an FK field and cannot be used in a simple SQL query.
XPO
不能用一个标准“[RelatedTo.Active] = true”在活动服务器上,因为ReatedTo不是一个外键,不能用一个简单SQL查询。

The second approach - Intermediate table.(第二种方法 中间表)

Create an intermediate table for the IActivityTarget interface:
IactivityTarget 接口建立一个中级表:

 

In this instance, the implementation of the IActivityTarget interface in the Account and Contact would redirect all calls to the aggregated ActivityTarget class:
在这个实例中,在客户和联系人中IactivityTarget接口的实现,将直接调用聚集ActivityTarget类:

public IList<IActivity> Activities {
    get { return ActivityTarget.Activities; }
}

Adding extra properties to the IActivityTarget interface won’t be a problem:
IactivityTarget接口添加扩展属性是没问题的:

public interface IActivityTarget {
    IList<IActivity> Activities { get; }
    bool Active { get; set; }
}

... because these properties will be contained in the ActivityTarget class, and the Contact and Account will redirect calls to it:
因为这些属性将包含在ActivityTarget类,联系人和客户将直接调用它:

public bool Active {
    get { return ActivityTarget.Active; }
    set { ActivityTarget.Active = value; }
}

But this approach yields another problem – since we hide an indirection, any Activity-related criteria for, say, a Contact (“Activities.Count > 0”) will not be evaluated on the server, because there is no association between the Contact and Activity. As an alternative, we could translate “Activites.Count” to “ActivityTarget.Activities.Count”. This translation might not be very obvious and would lead to problems.
但,这种方法会产生另一个问题 由于我们隐藏一个间接,任何活动相关的标准,也就是说,一个联系人(“Activities.Count > 0”)将不会在服务器上赋值,因为,联系人和活动之间没有关联。作为替代方案,我们将转换Activites.Count” “ActivityTarget.Activities.Count”。这种事务,可能不太明显,将导致问题。

As you can see, that is something for XPO developers to think about. I’m going to talk to them and consider the approach we should use...
正如你看到的,只是XPO开发者要思考的问题。我将讨论它们,考虑使用这种方法

Good news! XPO can use persistent aliases to map the Account’s Activities to its ActivityTarget.Actvities. So, we will use the Intermediate Table approach with aliases, and we might not have problems related to criteria. My Activity components can work as is and I can proceed further while waiting for the XpoBuilder to generate what I want.
好消息!XPO能用持久化映射Account’s状态到它的ActivityTarget.Activities。因此,我将用中间表方法,我们可能没有相关标准问题。我的活动组件是工作的,我可以在等待进行进一步的XpoBuilder生成我想要的。

 

欢迎转载,转载请注明出处:http://www.cnblogs.com/Tonyyang/

posted on 2011-01-26 08:42  Tonyyang  阅读(685)  评论(0编辑  收藏  举报
欢迎转载,转载请注明出处:http://www.cnblogs.com/Tonyyang/