Silverlight 考勤演示(使用 Sterling Silverlight 数据库)






4.96/5 (24投票s)
一个使用 Silverlight Sterling 数据库和视图模型 (MVVM) 的演示应用程序。
一个 Silverlight 数据库
Silverlight 的一个特性是独立存储。它允许您的 Silverlight 程序将几乎任何类型的数据或对象本地存储在用户的计算机上。基本上,它就像一个非常大的网页 cookie。有些人称之为“大 Cookie”。
您希望这样做的原因是它对用户来说更快。它对于 Windows Phone 7 应用程序也非常有用。独立存储的问题是,与它的交互非常原始。这就是适用于 Silverlight 和 Windows Phone 7 的 Sterling 独立存储数据库与 LINQ 项目能够提供帮助的地方。它允许我们在独立存储中存储和检索数据(几乎)就像在普通关系数据库中一样。
Sterling 独立存储数据库
由 Jeremy Likness 创建的适用于 Silverlight 和 Windows Phone 7 的 Sterling 独立存储数据库与 LINQ 项目提供了以下功能
- 它是免费的 - 夫复何求?
- 它提供完整的源代码 - 因此您的项目没有风险。
- 出色的文档 - 对于如此复杂的项目,这非常重要。它还附带示例、Silverlight 浏览器外和 Windows Phone 7 项目。
- 它具有非常快的性能并具有索引 - 使用独立存储的目的是速度(和便利性)。CodePlex 站点上项目下载中的示例项目展示了代码如何快速处理大量数据。
- 小巧轻便 - 这包括大小和功能。它有很多功能,但它只处理数据的读取和写入,以及与这些目标相关的任何内容。您不必使用视图模型、MVVM 或特定的设计模式。
请注意,您可以通过此链接找到其他免费的开源 Silverlight 对象数据库。
使用对象数据库
使用 Sterling 非常简单。您首先定义您的表。接下来,您将数据(对象)保存到您的表中,如下所示
SterlingService.Current.Database.Save(
new Attendance()
{
AttendanceKey = "1-23324353453534",
EnrollmentId = 1,
AttendanceDate = new DateTime(2010, 1, 1),
AttendanceStatus = "P"
});
要更新对象,只需使用现有对象的键保存对象即可。
现在,要获取您的数据,您在使用像 Sterling 这样的对象数据库时必须调整您的思维方式。您不能只定义一个带字段的对象,然后查询您想要的任何字段来获取数据。
您必须使用实体的键来获取数据
Attendance objAttendanceRecord =
SterlingService.Current.Database.Load<Attendance>("1-23324353453534");
或者使用您创建的预定义索引查询它,例如 "ATTENDANCE_ENROLLMENTIDANDDATE
"
var result = (from Attendance in SterlingService.Current.Database.Query<Attendance,
int, DateTime, string>(AttendanceDatabase.ATTENDANCE_ENROLLMENTIDANDDATE)
where Attendance.Index.Item1 == EnrollmentId
where Attendance.Index.Item2 == AttendanceDate
select Attendance).FirstOrDefault();
示例应用程序:简单考勤
为了演示 Sterling 的功能,我决定使用一个虽小但不简单的示例。这个考勤应用程序很小,但并非没有复杂性。您可以选择一天,并且只会看到当天注册的学生。当您输入考勤状态时,它会立即保存。
在某些情况下,您正在创建新的考勤记录,而在某些情况下,您正在更新考勤记录。程序无缝处理一切,并以您点击鼠标的速度移动。
该应用程序主要由 Student
类、Enrollment
类和 Attendance
类组成。Sterling 没有外键,但正如您稍后将看到的,我们能够创建实体之间的连接。
设置您的项目
Sterling 附带的文档是一流的,所以您会想阅读它。本文将向您介绍在项目中使用 Sterling 所需步骤的总体概念。
第一步是引用 Sterling 项目,或将其程序集包含在您的项目中(您可以从其下载页面下载程序集的安装程序)。
接下来,您创建一个继承自 BaseDatabaseInstance
的类。然后您定义您的表和索引
protected override List<ITableDefinition> _RegisterTables()
{
return new List<ITableDefinition>
{
CreateTableDefinition<Student, int>(fg => fg.StudentId).WithIndex<Student,
string, int>(STUDENT_NAME, fg => fg.Name),
CreateTableDefinition<Enrollment,
int>(fd => fd.EnrollmentId).WithIndex<Enrollment,
int, int>(ENROLLMENT_ENROLLMENTID, fd =>fd.EnrollmentId).WithIndex<Enrollment,
DateTime, int>(ENROLLMENT_STARTDATE, fd =>fd.StartDate).WithIndex<Enrollment,
DateTime, int>(ENROLLMENT_STOPDATE, fd =>fd.StopDate),
CreateTableDefinition<Attendance,
string>(fd => fd.AttendanceKey).WithIndex<Attendance,
int, DateTime, string>(ATTENDANCE_ENROLLMENTIDANDDATE,
fd =>
Tuple.Create(fd.EnrollmentId, fd.AttendanceDate)),
};
}
您可以将 SterlingService
类复制并粘贴到您的项目中,只需更改一行即可注册您刚刚创建的数据库类(在此示例中为 AttendanceDatabase
)
public void Starting()
{
if (DesignerProperties.IsInDesignTool) return;
_engine.Activate();
// Put the type of the Database Class after RegisterDatabase<
Database = _engine.SterlingDatabase.RegisterDatabase<AttendanceDatabase>();
}
然后将以下行添加到您的 app.xaml 页面以启动服务
<Application.ApplicationLifetimeObjects>
<SimpleAttendance:SterlingService/>
</Application.ApplicationLifetimeObjects>
我发现如果有人禁用他们的独立存储,应用程序将无法工作(人们有权禁用独立存储,就像他们可以禁用网页浏览器 cookie 一样),所以我将此包含在 app.xaml.cs 文件中
public App()
{
this.Startup += this.Application_Startup;
UnhandledException += Application_UnhandledException;
try
{
// Test for Isolated Storage
using (IsolatedStorageFile oStore =
IsolatedStorageFile.GetUserStoreForApplication())
{
InitializeComponent();
}
}
catch (IsolatedStorageException)
{
MessageBox.Show("You need to right-click and " +
"enable Isolated Storage to use this application");
}
}
就这样,您已经设置完毕!
给我一些示例数据!
此示例应用程序使用视图模型 (MVVM) 设计模式。它加载时所做的第一件事是 InvokeCommandAction
行为(设置为当用户控件“加载”时触发)调用 LoadDatabaseCommand
(ICommand
)。
如果 LoadData
方法检测到学生数据库表为空,它会调用 SetupData
方法
public void LoadData()
{
bool hasKeys = false;
foreach (var item in SterlingService.Current.Database.Query<Student, int>())
{
hasKeys = true;
break;
}
if (!hasKeys)
{
SetupData();
}
SetupData
创建所有Student
及其Enrollment
。它只创建一个示例Attendance
记录
private void SetupData()
{
// Students
var StudentData = new List<Student>()
{
new Student() { StudentId = 1 , Name = "Student One" },
new Student() { StudentId = 2 , Name = "Student Two" },
new Student() { StudentId = 3 , Name = "Student Three" },
new Student() { StudentId = 4 , Name = "Student Four" },
new Student() { StudentId = 5 , Name = "Student Five" },
new Student() { StudentId = 6 , Name = "Student Six" },
new Student() { StudentId = 7 , Name = "Student Seven" },
new Student() { StudentId = 8 , Name = "Student Eight" },
new Student() { StudentId = 9 , Name = "Student Nine" },
new Student() { StudentId = 10 , Name = "Student Ten" },
};
foreach (var item in StudentData)
{
SterlingService.Current.Database.Save(item);
}
// Enrollments
var EnrollmentData = new List<Enrollment>()
{
new Enrollment() {EnrollmentId = 1, StudentId = 1,
StartDate = new DateTime(2010,1,1),
StopDate = new DateTime(2010,2,1) },
new Enrollment() {EnrollmentId = 2, StudentId = 2,
StartDate = new DateTime(2010,1,1),
StopDate = new DateTime(2010,2,11) },
new Enrollment() {EnrollmentId = 3, StudentId = 3,
StartDate = new DateTime(2010,1,3),
StopDate = new DateTime(2010,2,1) },
new Enrollment() {EnrollmentId = 4, StudentId = 4,
StartDate = new DateTime(2010,1,3),
StopDate = new DateTime(2010,2,1) },
new Enrollment() {EnrollmentId = 5, StudentId = 5,
StartDate = new DateTime(2010,1,10),
StopDate = new DateTime(2010,2,15) },
new Enrollment() {EnrollmentId = 6, StudentId = 6,
StartDate = new DateTime(2010,1,3),
StopDate = new DateTime(2010,2,1) },
new Enrollment() {EnrollmentId = 7, StudentId = 7,
StartDate = new DateTime(2010,1,22),
StopDate = new DateTime(2010,2,1) },
new Enrollment() {EnrollmentId = 8, StudentId = 8,
StartDate = new DateTime(2010,1,1),
StopDate = new DateTime(2010,2,1) },
new Enrollment() {EnrollmentId = 9, StudentId = 9,
StartDate = new DateTime(2010,1,1),
StopDate = new DateTime(2010,2,1) },
new Enrollment() {EnrollmentId = 10, StudentId = 10,
StartDate = new DateTime(2010,1,1),
StopDate = new DateTime(2010,2,1) }
};
foreach (var item in EnrollmentData)
{
SterlingService.Current.Database.Save(item);
}
// Sample Attendance
SterlingService.Current.Database.Save(new Attendance()
{
AttendanceKey = String.Format("{0}-{1}", "1",
new DateTime(2010, 1, 1).Ticks.ToString()),
EnrollmentId = 1,
AttendanceDate = new DateTime(2010, 1, 1),
AttendanceStatus = "P"
});
}
请注意,对于 Attendance
记录,我们不简单地使用整数作为计数器字段。我们创建一个由 Enrollment
和以 Tick
表示的 Date
组成的键(String.Format("{0}-{1}", "1", new DateTime(2010, 1, 1).Ticks.ToString())
)。这是真正的键,因为我们绝不希望数据库中同一天有相同 Enrollment
的 Attendance
记录。
回到 LoadData
方法中,我们将学生和注册“表”的内容加载到视图模型中的公共集合中。这将允许我们使用 LINQ to Objects 轻松查询数据。
foreach (var item in SterlingService.Current.Database.Query<Student, int>())
{
Students.Add(item.LazyValue.Value);
}
foreach (var item in SterlingService.Current.Database.Query<Enrollment, int>())
{
Enrollments.Add(item.LazyValue.Value);
}
IsDataLoaded = true;
GetAttendanceForDay();
但是,我们不会加载 Attendance
记录,因为即使像这样的小演示,也可能有很多 Attendance
记录。我们使用此代码检索所选日期的已注册学生
private void GetAttendanceForDay()
{
colAttendanceForDay.Clear();
var result = from Enrollment in Enrollments
from Student in Students
where Enrollment.StudentId == Student.StudentId
where Enrollment.StartDate >= AttendanceDate
where Enrollment.StopDate <= AttendanceDate
select new AttendanceForDay
{
AttendanceKey = String.Format("{0}-{1}",
Enrollment.EnrollmentId, AttendanceDate.Ticks.ToString()),
AttendanceStatus =
GetAttendanceStatus(Enrollment.EnrollmentId, AttendanceDate),
EnrollmentId = Enrollment.EnrollmentId,
Name = Student.Name,
StudentId = Student.StudentId
};
foreach (var item in result)
{
// Add an instance of this View Model to the object
// This will allow us to get an instance of the View Model
// to raise events such as the UpdateAttendanceCommand
item.objMainPageModel = this;
colAttendanceForDay.Add(item);
}
}
该查询调用此方法,该方法针对我们为表定义的 Index
(ATTENDANCE_ENROLLMENTIDANDDATE
) 进行查询,以获取考勤状态
private static string GetAttendanceStatus(int EnrollmentId, DateTime AttendanceDate)
{
var result = (from Attendance in SterlingService.Current.Database.Query<Attendance,
int, DateTime, string>(AttendanceDatabase.ATTENDANCE_ENROLLMENTIDANDDATE)
where Attendance.Index.Item1 == EnrollmentId
where Attendance.Index.Item2 == AttendanceDate
select Attendance).FirstOrDefault();
if (result != null)
{
return result.LazyValue.Value.AttendanceStatus;
}
else
{
return "";
}
当我们需要考勤数据时,我们只从 Sterling 数据库中一次拉取一条记录。但是,它非常快,因为数据位于用户的计算机上。
关于单选按钮的注意事项
所有单选按钮都绑定到同一个字段 (AttendanceStatus
)。它们各自使用两个值转换器。一个为其 GroupName
创建一个唯一名称(在使用带视图模型 (MVVM) 的单选按钮时需要),另一个将其绑定到一个可能的单值(例如“P”表示“Present”)。
在绑定中,我们指定值转换器,并指定一个转换器参数。每个单选按钮使用不同的参数。这是 IsChecked
字段的值转换器
public class AttendanceConverter : IValueConverter
{
#region Methods
public object Convert(object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
if (value == null || parameter == null)
{
return value;
}
return value.ToString() == parameter.ToString();
}
public object ConvertBack(object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
if (value == null || parameter == null)
{
return value;
}
return (String)parameter;
}
#endregion Methods
}
另请注意,DataGrid
必须设置为只读,以防止用户单击 DataGrid
而不是单选按钮时发生绑定错误(这样一来,单行中可以选中多个单选按钮)。
每个按钮上的一个行为通过调用以下 ICommand
来更新考勤
#region UpdateAttendanceCommand
public ICommand UpdateAttendanceCommand { get; set; }
public void UpdateAttendance(object param)
{
AttendanceForDay objAttendanceForDay = (AttendanceForDay)param;
// Get the Attendnace record if there is one
Attendance objAttendanceRecord =
SterlingService.Current.Database.Load<Attendance>(
objAttendanceForDay.AttendanceKey);
if (objAttendanceRecord == null) // New Record
{
// If Attendance Record was not found create a new record
Attendance NewAttendanceRecord = new Attendance();
NewAttendanceRecord.AttendanceKey = objAttendanceForDay.AttendanceKey;
NewAttendanceRecord.AttendanceDate = AttendanceDate;
NewAttendanceRecord.EnrollmentId = objAttendanceForDay.EnrollmentId;
NewAttendanceRecord.AttendanceStatus = objAttendanceForDay.AttendanceStatus;
SterlingService.Current.Database.Save(NewAttendanceRecord);
SterlingService.Current.Database.Flush();
}
else // Update Record
{
// Set Attendanace status and save the record
objAttendanceRecord.AttendanceStatus = objAttendanceForDay.AttendanceStatus;
SterlingService.Current.Database.Save(objAttendanceRecord);
SterlingService.Current.Database.Flush();
}
}
强大的工具可以节省时间并减少错误
使用像 Sterling 这样的数据库的一个好方法是允许用户输入考勤,然后点击一个按钮将其传输到中央服务器。任何冲突都可以在辅助步骤中解决。也许会弹出一个屏幕显示任何冲突,并允许用户决定是否要覆盖记录。好处是应用程序会运行得非常快,并且用户永远不会“超时”。如果他们的计算机断电,他们也不会丢失任何记录。
像这样的应用程序展示了 Silverlight 在业务线 (LOB) 应用程序方面的真正强大功能。