上一次的日记中我们详细讨论了Entity Framework Code First如何建立表之间的一对多关系。这次的日记中我们将详细介绍Entity Framework Code First建立多对多关系的默认行为,以及如何通过Fluent API改变默认行为。
本次日记主要介绍一下内容:
1.Entity Framework Code First在什么情况下会建立表之间的多对多关系,以及建立多对多关系时的默认配置。
2.如何通过Fluent API更改Entity Framework Code First建立多对多关系时的默认配置。
1.Entity Framework Code First在什么情况下会建立表之间的多对多关系,以及建立多对多关系时的默认配置。
假设我们的业务领域再次发生了改变,客户要求我们记录每种产品的打折信息。根据我们和客户的交谈,我们可以得到如下的业务逻辑:
每个产品种类需要保留所有和它相关的打折促销记录。每次打折促销都有固定的开始时间,结束时间,促销的产品列表以及打折的折扣率。
我们根据这个业务领域的变化重新改写了我们的ProductCatalog类:
public class ProductCatalog
{
public int ProductCatalogId { get; set; }
public string CatalogName { get; set; }
public string Manufactory { get; set; }
public decimal ListPrice { get; set; }
public decimal NetPrice { get; set; }
public List<Product> ProductInStock { get; set; }
public List<SalesPromotion> SalesPromotionHistory { get; set; }
public ProductCatalog()
{
SalesPromotionHistory = new List<SalesPromotion>();
ProductInStock = new List<Product>();
}
public Product GetProductInStock()
{
if (ProductInStock.Count <= 0)
{
throw new Exception("No product in stock");
}
Product product = ProductInStock[0];
ProductInStock.RemoveAt(0);
return product;
}
public void PurchaseProduct(List<Product> products)
{
ProductInStock.AddRange(products);
}
public void PurchaseProduct(List<Product> products,decimal newListPrice,decimal newNetPrice)
{
ProductInStock.AddRange(products);
ListPrice = newListPrice;
NetPrice = newNetPrice;
}
public decimal GetPromotionPrice()
{
if (SalesPromotionHistory.Count <= 0)
{
return ListPrice;
}
decimal listPrice = 0;
foreach (var promotion in SalesPromotionHistory)
{
if (promotion.StartDate <= DateTime.Now && promotion.EndDate >= DateTime.Now)
{
listPrice = ListPrice * promotion.SalesDiscount;
}
}
return listPrice;
}
}.csharpcode, .csharpcode pre{font-size: small;color: black;font-family: consolas, "Courier New", courier, monospace;background-color: #ffffff;/*white-space: pre;*/}.csharpcode pre { margin: 0em; }.csharpcode .rem { color: #008000; }.csharpcode .kwrd { color: #0000ff; }.csharpcode .str { color: #006080; }.csharpcode .op { color: #0000c0; }.csharpcode .preproc { color: #cc6633; }.csharpcode .asp { background-color: #ffff00; }.csharpcode .html { color: #800000; }.csharpcode .attr { color: #ff0000; }.csharpcode .alt {background-color: #f4f4f4;width: 100%;margin: 0em;}.csharpcode .lnum { color: #606060; }
我们也根据业务领域定义了我们的SalesPromotion类
public class SalesPromotion{
public int SalesPromotionId { get; set; }
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
public decimal SalesDiscount { get; set; }
public List<ProductCatalog> PromotionProductCatalog { get; set; }
public SalesPromotion()
{
PromotionProductCatalog = new List<ProductCatalog>();
}
public void AddProductCatalogToPromotion(ProductCatalog catalog)
{
catalog.SalesPromotionHistory.Add(this);
PromotionProductCatalog.Add(catalog);
}
}.csharpcode, .csharpcode pre{font-size: small;color: black;font-family: consolas, "Courier New", courier, monospace;background-color: #ffffff;/*white-space: pre;*/}.csharpcode pre { margin: 0em; }.csharpcode .rem { color: #008000; }.csharpcode .kwrd { color: #0000ff; }.csharpcode .str { color: #006080; }.csharpcode .op { color: #0000c0; }.csharpcode .preproc { color: #cc6633; }.csharpcode .asp { background-color: #ffff00; }.csharpcode .html { color: #800000; }.csharpcode .attr { color: #ff0000; }.csharpcode .alt {background-color: #f4f4f4;width: 100%;margin: 0em;}.csharpcode .lnum { color: #606060; }
我们可以写一次单元测试方法,测试一下Entity Framework Code First会根据类的定义建立出怎样的数据表关系。
[TestMethod]public void CanAddSalesPromotion()
{
OrderSystemContext unitOfWork = new OrderSystemContext();
ProductRepository repository = new ProductRepository(unitOfWork);
ProductCatalog catalog = repository.GetProductCatalogById(1);
SalesPromotion promotion = new SalesPromotion { StartDate = DateTime.Parse("2013-1-18"), EndDate = DateTime.Parse("2013-1-25"), SalesDiscount = 0.75M };
promotion.AddProductCatalogToPromotion(catalog);
unitOfWork.CommitChanges();
}.csharpcode, .csharpcode pre{font-size: small;color: black;font-family: consolas, "Courier New", courier, monospace;background-color: #ffffff;/*white-space: pre;*/}.csharpcode pre { margin: 0em; }.csharpcode .rem { color: #008000; }.csharpcode .kwrd { color: #0000ff; }.csharpcode .str { color: #006080; }.csharpcode .op { color: #0000c0; }.csharpcode .preproc { color: #cc6633; }.csharpcode .asp { background-color: #ffff00; }.csharpcode .html { color: #800000; }.csharpcode .attr { color: #ff0000; }.csharpcode .alt {background-color: #f4f4f4;width: 100%;margin: 0em;}.csharpcode .lnum { color: #606060; }
我们打开数据库看一下,看看Entity Framework Code First从类的定义映射出来的数据库结构是怎样的?
大家可以看到由于我们的SalesPromotion和ProductCatalog类中都包含对方类的实例集合,在这种情况下,Entity Framework Code First默认地会将类之间的引用关系映射为数据库表之间的多对多关系。Entity Framework会建立一个多对多的连接表,表的名字是两个关联表的名字加在一起,然后按照它自己的规则去给你加上复数形式。表中的两个字段,既是引用两个关联表的外键,并且也作为这个新的连接表的联合主键。
但是大家可能会发现连接表的名字和连接表中字段的名字都是Entity Framework Code First按照自己的规则定义的,我们可以对它们进行修改。
2.如何通过Fluent API更改Entity Framework Code First建立多对多关系时的默认配置
我们可以通过上一次日记我们介绍的Has和With方法去配置连接表的名字和连接表中两个外键列的名字。
public class SalesPromotionEntityConfiguration:EntityTypeConfiguration<SalesPromotion>{
public SalesPromotionEn
.NET中的事件其实就是一个观察者模式(Observer Pattern)的一个语法上的快捷实现(更多可以参考:使用委托和事件实现观察者模式)。事件是一种内建的委托,用来为事件处理函数提供类型安全的方法签名。事件就是对象将信息告知观察者的方式。
1.发布者定义事件
我们来看一个例子,有一个日志类,将应用程序需要分发的信息发送个各个侦听着,这些侦听者可以是控制域、系统日志、数据库等等,首先定义一个在事件触发中复制传递消息的事件参数类:
2 {
3 public string message { get; private set; }
4 public int Priority { get; private set; }
5
6 public LoggerEventArgs(int p, string m)
7 {
8 this.Priority = p;
9 this.message = m;
10 }
11 }
然后是日志类本身:
2 {
3 static Logger()
4 {
5 theOnly = new Logger();
6 }
7
8 private Logger()
9 {
10 }
11
12 private static Logger theOnly = null;
13 public static Logger Singleton
14 {
15 get { return theOnly; }
16 }
17 //定义事件
18 public event EventHandler<LoggerEventArgs> Log;
19 //在这里增加消息和日志
20 public void AddMsg(int priority, string msg)
21 {
22 //这里引用临时变量是一个重要的安全措施,可预防多线程环境中的竞争条件
23 //若是没有引用的副本,客户代码可能会在if判断语句和事件处理函数之间移
24 //除事件处理函数,而复制引用之后即可避免这种情况
25 EventHandler<LoggerEventArgs> l = Log;
26 if (l != null)
27 l(this, new LoggerEventArgs(priority, msg));
28 }
29 }
在这里AddMsg()是触发事件的方法,LoggerEventArgs类中定义了事件的优先级和消息内容,委托则为事件处理函数定义了签名。在Logger类内部,事件自动Log定义了事件处理函数。编译器看到这个字段后会自动创建对应的Add和Remove操作符,编译器生成的代码和下面类似:
2 {
3 private EventHandler<LoggerEventArgs> log;
4
5 public event EventHandler<LoggerEventArgs> Log
6 {
7 add { log = log + value; }
8 remove { log = log - value; }
9 }
10 public void AddMsg(int priority, string msg)
11 {
12 EventHandler<LoggerEventArgs> l = log;
13 if (l != null)
14 l(this, new LoggerEventArgs(priority, msg));
15 }
16 }
或者我们可以直接查看IL代码:
2.侦听者订阅事件
我们可以把日志信息订阅到标准错误控制台输出:
ASP.NET中的Eval和DataBinder.Eval方法bind是双向绑定,但需数据源可更改才能用。ASP.NET 2.0改善了模板中的数据绑定操作,把v1.x中的数据绑定语法DataBinder.Eval(Container.DataItem, fieldname)简化为Eval(fieldname)。Eval方法与DataBinder.Eval一样可以接受一个可选的格式化字符串参数。缩短的Eval语法与DataBinder.Eval的不同点在于,Eval会根据最近的容器对象(例如DataListItem)的DataItem属性来自动地解析字段,而DataBinder.Eval需要使用参数来指定容器。由于这个原因,Eval只能在数据绑定控件的模板中使用,而不能用于Page(页面)层。当然,ASP.NET 2.0页面中仍然支持DataBinder.Eval,你可以在不支持简化的Eval语法的环境中使用它。
用法<%# Bind("Subject") %> //绑定字段
<%# Container.DataItemIndex + 1%> //实现自动编号
<%# Container.ItemIndex %> //Repeater自动编号
<%# DataBinder.Eval(Container.DataItem, "[n]") %>
通常使用的方法(這三個性能最好)
<%# DataBinder.Eval(Container.DataItem, "ColumnName") %>
<%# DataBinder.Eval(Container.DataItem, "ColumnName", null) %>
<%# DataBinder.Eval(Container, "DataItem.ColumnName", null) %>
其他用法
<%# ((DataRowView)Container.DataItem)["ColumnName"] %>
<%# ((DataRowView)Container.DataItem).Row["ColumnName"] %>
<%# ((DataRowView)Container.DataItem)["adtitle"] %>
<%# ((DataRowView)Container.DataItem)[n] %>
<%# ((DbDataRecord)Container.DataItem)[0] %>
<%# (((自定义类型)Container.DataItem)).属性.ToString() %>//如果属性为字符串类型就不用ToString()了
Eval用法
DataBinder.Eval范例
<%# DataBinder.Eval(Container.DataItem, "IntegerValue", "{0:c}") %>
格式化字符串参數是可选的。如果忽略参数,DataBinder.Eval 返回对象类型的值,
//显示两位小数
<%# DataBinder.Eval(Container.DataItem, "UnitPrice", "${0:F2}") %>
格式:
{0:d} 日期只显示年月日
{0:yyyy-mm-dd} 按格式显示年月日
{0:c} 货币样式
<%#Container.DataItem("price","{0:¥#,##0.00}")%>
<%# DataBinder.Eval(Container.DataItem,"Company_Ureg_Date","{0:yyyy-M-d}")%>
Specifier Type Format Output (Passed Double 1.42) Output (Passed Int -12400)
c Currency {0:c} $1.42 -$12,400
d Decimal {0:d} System.FormatException -12400
e Scientific {0:e} 1.420000e+000 -1.240000e+004
f Fixed point {0:f} 1.42 -12400.00
g General {0:g} 1.42 -12400
n Number with commas for thousands {0:n} 1.42 -12,400
r Round trippable {0:r} 1.42 System.FormatException
x Hexadecimal {0:x4} System.FormatException cf90
{0:d} 日期只显示年月日
{0:yyyy-mm-dd} 按格式显示年月日
样式取决于 Web.config 中的设置
{0:c} 或 {0:£0,000.00} 货币样式,标准英国货币样式
显示为£3,000.10
{0:c} 或 string.Format("{0:C}", price); 中国货币样式
显示为 ¥3,000.10
{0:c} 或 string.Format("{0:C}", price); 美国货币样式
Eval(" ")绑定两个字段:
CommandArgument='<%#Eval("dyid").ToString()+Eval("dyid1").ToString()+… %>'这种形式就行
你把Eval("").ToString()当成一个普通字符串,而'<%# %>'保留就行,你高兴怎么处理就怎么处理,
所有字符串的操作都有效,你还可以用静态函数来处理这些字符串。
<asp:TextBox ID="TextBox5" runat="server"
Text='<%# Eval("RZMJ").ToString()+Eval("LDDW").ToString() %>'></asp:TextBox>区别:绑定表达式
<%# Eval("字段名") %>
<%# Bind("字段名") %>
区别 eval是只读数据,Bind是可更新的.(可以读取和写入)对于程序员来说最根本的区别是:
1.Bind是可更新的,是Bind绑定列可编辑,并且可以和数据源控件交互,直接和数据库交互,但是用Bind的话,程序端的自定义操作就不能用了,比如Convert,ToString()等,或自己写的函数,在程序端都不可以
2.eval是只读数据,用eval的话不可以和数据源控件交互,是单向的,但是可以自定义操作
比如上面的<%# Eval("字段名").ToString().Trim()%>
本人从来和数据库交互自己写代码,所以对我来说操作的Eval更方便,基本不用Bind,而且以前写过,绑定泛型的时候
Bind都不支持嵌套类
下面接受啊<%%> <%= %><% %> 放在 HTML中写后台程序代码.
<%= %> 放在HTML中输出一个变量,表达式。。
本文链接