October 2009 - Posts
בפרק הקודם הגענו למצב שיש לנו טבלה אחת שממופה לטבלה
כהמאפיין InternalGender הוגדר כ - private והוספנו partial class כדי לחשוף אותו כ - enum
public enum UserGender
{
Male,
Female
}
public partial class User
{
public UserGender Gender
{
get
{
return (UserGender)this.InternalGender;
}
set
{
InternalGender = (int)value;
}
}
}
נפתח את המודל בעזרת XmlEditor
מומלץ לסדר את ה - xml על ידי לחיצה על Format the whole document. (צירוף המקשים Ctrl+e+d)
לאחר מכן נלחץ קליק ימין בעכבר ונבחר ב - Outlining ואז ב- Toggle all outlining
כעת כל ה - xml יהיה מכווץ, נרחיב את ה - xml על ידי לחיצה על הסימן ה - + ונראה את החלק של ה - designer והחלק של ה - Runtime (כבר דברנו על זה קצת
בפרק הראשון)
כשנרחיב את החלק של edmx:RunTime נראה את הקוד הבא
כמו שכבר למדנו -
SSDL מתאר את את הסכמה של הנתונים (הסיבה שקוראים לזה SSDL כלומר - Store Schema Definition Language ולא DDSL כלומר Data Schema Definiton Language היא מכיון שכשפיתחו את Entity Framework הם חשבו על העתיד ולא כל מידע חייב להגיע מבסיס נתונים - ולכן הם קראו לזה Store (אולי זה יהיה xml או מקור נתונים אחר)
נפתח את ה - + של StorageModel, נגלה אלמנט שנקרא Schema - בברירת מחדל הוא נראה כך:
<Schema Namespace="EFLabModel.Store"
Alias="Self"
Provider="System.Data.SqlClient"
ProviderManifestToken="2005"
xmlns:store="http://schemas.microsoft.com/ado/2007/12/edm/EntityStoreSchemaGenerator"
xmlns="http://schemas.microsoft.com/ado/2006/04/edm/ssdl">
תוכלו לקרוא
כאן על כל האלמנטים של ה - Schema (אני גם ארחיב עליהם בעתיד)
אפשר לראות את הסכמה של הסכמה - כלומר איזה אלמנטים יכולים להיות בחלק של ה - SSDL בנתיב הבא:
C:\Program Files\Microsoft Visual Studio 9.0\Xml\Schemas\System.Data.Resources.SSDLSchema.xsd
מעבר זריז:
Namespace: בדרך כלל השם של בסיס הנתונים בתוספת השם Store לאחר מכן במיפוי צריך את ה - namespace כדי להגיע לאלמנטים הפנימיים שהוגדרו במודל
Alias: כברירת מחדל Self - די ברור, במקום לכתוב בכל מקום (בחלק של ה - SSDL) את ה - namespace ניתן להתייחס ל - Alias לדוגמא: נוכל לכתוב
<EntitySet Name="User" EntityType="EFLabModel.Store.User" store:Type="Tables" Schema="dbo" />
או שנוכל לכתוב
<EntitySet Name="User" EntityType="Self.User" store:Type="Tables" Schema="dbo" />
שימו לב להתייחסות במאפיין EntityType.
Provider: מגדיר מיהו ספק הנתונים שלנו כמובן שזה לא חייב להיות SqlServer וזה יכול להיות גם
Oracle ועוד הרבה אחרים (אין ספק נתונים ל - Access)
למידע על Providers
בתוך האלמנט Schema נראה שני אלמנטים: הראשון EntityContainer נראה כך:
<EntityContainer Name="EFLabModelStoreContainer">
<EntitySet Name="User" EntityType="EFLabModel.Store.User" store:Type="Tables" Schema="dbo" />
</EntityContainer>
ה -
EntityContainer מאגד טבלאות וקשרים - כלומר כשיהיו לנו טבלאות עם קשרים ביניהם אנחנו נראה ליד האלמנט EntitySet גם אלמנטים מסוג AssociationSet שמגדירים קשרים (בפרקים הבאים).
לאלמנט EntityContainer יש מאפיין
Name שמזהה אותו. כך שנוכל למפות אותו לישות במודל שלנו.
ה -
EntitySet מתאר טבלה אחת (או View) בבסיס הנתונים (הוא לא ממש מתאר את הטבלה זה תפקידו של ה - EntityType - יותר נכון להגיד שהוא מגדיר את הטבלה ומקשר אליה טבלאות אחרות אם צריך) יש לו את המאפיינים Name (שבדרך כלל יהיה השם של הטבלה) EntityType (שמיד נראה כיצד הוא מתאר לפרטי פרטים את הטבלה) ומגדיר את סוג ה - EntityType (האם זה טבלה או View).
אחד הסיבות שאי אפשר היה להסתפק רק ב - EntityType וצריך גם את ה - EntitySet היא כדי שיהיה
היכן להגדיר שאילתות כלומר - במידה ונרצה להוסיף למודל שאילתה ישירה מול בסיס הנתונים נוכל להוסיך אותה בתוך ה - EntitySet (זהו נושא מתקדם ונראה את זה בפרקים הבאים), וכמובן גם עבור ירושות.
<EntityType Name="User">
<Key>
<PropertyRef Name="Id" />
</Key>
<Property Name="Id" Type="bigint" Nullable="false" StoreGeneratedPattern="Identity" />
<Property Name="Name" Type="nvarchar" Nullable="false" MaxLength="50" />
<Property Name="PenName" Type="nvarchar" MaxLength="50" />
<Property Name="Picture" Type="image" />
<Property Name="Email" Type="varchar" Nullable="false" MaxLength="50" />
<Property Name="Gender" Type="int" Nullable="false" />
</EntityType>
מתאר בצורה הכי פשוטה את מבנה הטבלה יש לו את ה -
Name (שהוא שם הטבלה בבסיס הנתונים) יש את אלמנט ה -
key שממופה לאחד מהעמודות בטבלה ויש תיאור של כל שאר העמודות.
כעת נעבור לחלק השני של ה - xml ה - CSDL (שמתאר את מבנה הישות)
כשנרחיב את ה -edmx:ConceptualModels נראה את האלמנט
Schema
<Schema Namespace="EFLabModel" Alias="Self" xmlns="http://schemas.microsoft.com/ado/2006/04/edm">
יש לו את המאפיינים של ה - namespace וה - alias שהמשמעות שלהם היא כמו ב - SSDL.
בתוך האלמנט נמצא את שני האלמנטים הבאים, הראשון:
EntityContainer שמכיל את כל ה - EntitySet שמתארות את הישויות ואת הקשרים ביניהם
כמובן שבתוך ה - EntityContainer נמצא את כל ה - AssociationSet שמתארים את הקשרים בין הישויות (בפרקים הבאים)
<EntityContainer Name="EFLabEntities1">
בתוך ה - EntityContiner נמצא את ה -
EntitySet
<EntitySet Name="User" EntityType="EFLabModel.User" />
אחד הסיבות שצריך EntitySet ולא מספיק EntityType היא עבור ירושות - כלומר אם יש לנו שני EntityType שיורשים אחד מהשני שניהם יהיו בתוך אותו EntitySet (נראה בפרקים הבאים)
בתוך ה - Schema EntityContainer יש גם את ה -
EntityType
<EntityType Name="User">
<Key>
<PropertyRef Name="Id" />
</Key>
<Property Name="Id" Type="Int64" Nullable="false" />
<Property Name="Name" Type="String" Nullable="false" MaxLength="50" Unicode="true"
FixedLength="false" />
<Property Name="PenName" Type="String" MaxLength="50" Unicode="true"
FixedLength="false" />
<Property Name="Picture" Type="Binary" MaxLength="Max" FixedLength="false" />
<Property Name="Email" Type="String" Nullable="false" MaxLength="50" Unicode="false"
FixedLength="false" />
<Property Name="InternalGender" Type="Int32" Nullable="false"
a:GetterAccess="Private"
a:SetterAccess="Private"
xmlns:a="http://schemas.microsoft.com/ado/2006/04/codegeneration" />
</EntityType>
הוא מתאר בצורה פשוטה את הישות - שימו לב למאפיין InternlGender שיש לו את ההגדרה private עבור ה - Getter וה - Setter, יש לו כמובן עוד הרבה מאפיינים ואלמנטים שנדבר עליהם בפרקים הבאים.
כעת נעבור לחלק האחרון של ה - xml שהוא המיפוי בין ה - SSDL להין ה - CSDL
כדי שלא תצטרכו לדלג בפוסט כדי לחפש את על מה אני מדבר - אני אציג כאן את שני חלקי ה - xml במלואם
<!-- SSDL content -->
<edmx:StorageModels>
<Schema Namespace="EFLabModel.Store"
Alias="Self"
Provider="System.Data.SqlClient"
ProviderManifestToken="2005"
xmlns:store="http://schemas.microsoft.com/ado/2007/12/edm/EntityStoreSchemaGenerator"
xmlns="http://schemas.microsoft.com/ado/2006/04/edm/ssdl">
<EntityContainer Name="EFLabModelStoreContainer">
<EntitySet Name="User" EntityType="EFLabModel.Store.User" store:Type="Tables" Schema="dbo" />
</EntityContainer>
<EntityType Name="User">
<Key>
<PropertyRef Name="Id" />
</Key>
<Property Name="Id" Type="bigint" Nullable="false" StoreGeneratedPattern="Identity" />
<Property Name="Name" Type="nvarchar" Nullable="false" MaxLength="50" />
<Property Name="PenName" Type="nvarchar" MaxLength="50" />
<Property Name="Picture" Type="image" />
<Property Name="Email" Type="varchar" Nullable="false" MaxLength="50" />
<Property Name="Gender" Type="int" Nullable="false" />
</EntityType>
</Schema>
</edmx:StorageModels>
<!-- CSDL content -->
<edmx:ConceptualModels>
<Schema Namespace="EFLabModel" Alias="Self" xmlns="http://schemas.microsoft.com/ado/2006/04/edm">
<EntityContainer Name="EFLabEntities1">
<EntitySet Name="User" EntityType="EFLabModel.User" />
</EntityContainer>
<EntityType Name="User">
<Key>
<PropertyRef Name="Id" />
</Key>
<Property Name="Id" Type="Int64" Nullable="false" />
<Property Name="Name" Type="String" Nullable="false" MaxLength="50" Unicode="true"
FixedLength="false" />
<Property Name="PenName" Type="String" MaxLength="50" Unicode="true"
FixedLength="false" />
<Property Name="Picture" Type="Binary" MaxLength="Max" FixedLength="false" />
<Property Name="Email" Type="String" Nullable="false" MaxLength="50" Unicode="false"
FixedLength="false" />
<Property Name="InternalGender" Type="Int32" Nullable="false"
a:GetterAccess="Private"
a:SetterAccess="Private"
xmlns:a="http://schemas.microsoft.com/ado/2006/04/codegeneration" />
</EntityType>
</Schema>
</edmx:ConceptualModels>
כעת נרחיב את האלמנט edmx:Mappings ונראה את ה - xml הבא:
<edmx:Mappings>
<Mapping Space="C-S" xmlns="urn:schemas-microsoft-com:windows:storage:mapping:CS">
<EntityContainerMapping StorageEntityContainer="EFLabModelStoreContainer"
CdmEntityContainer="EFLabEntities1">
<EntitySetMapping Name="User">
<EntityTypeMapping TypeName="IsTypeOf(EFLabModel.User)">
<MappingFragment StoreEntitySet="User">
<ScalarProperty Name="Id" ColumnName="Id" />
<ScalarProperty Name="Name" ColumnName="Name" />
<ScalarProperty Name="PenName" ColumnName="PenName" />
<ScalarProperty Name="Picture" ColumnName="Picture" />
<ScalarProperty Name="Email" ColumnName="Email" />
<ScalarProperty Name="InternalGender" ColumnName="Gender" />
</MappingFragment>
</EntityTypeMapping>
</EntitySetMapping>
</EntityContainerMapping>
</Mapping>
</edmx:Mappings>
האלמנט הראשון שנמצא הוא
Mapping הוא האלמנט שמכיל את כל מה שקשור למיפוי, המאפיין
Space אין לו משמעות מיוחדת הוא תמיד יהיה C-S כלומר ה - C של CSDL וה - S של SSDL
בתוכו יש לנו את ה -
EntityContainerNapping שני המאפיינים שלו מקשרים בין ה - EntityContainer של ה - CSDL לבין ה - EntityContainer של ה - SSDL
בנוסף אנחנו נראה בפרקים הבאים איך נקשר בין stored procedure לבין פונקציה שהוגדרה במודל הישויות.
בתוך ה - EntityContainerMapping יש לנו את ה -
EntitySetMapping שממפה בין ה - EntitySet של ה - SSDL לבין המקביל שלו ב - CSDL.
EntityTypeMapping ממפים בין ה - EntityTypes של ה - CSDL וה - SSDL המאפיין TypeName היה יכול להיכתב גם בצורה הזאת
<EntityTypeMapping TypeName="EFLabModel.User">
הסיבה לשימוש ב - IsTypeOf היא כשאנחנו עובדים עם ירושה והאובייקט שממופה יכול לרשת מ - User (בפרקים הבאים ארחיב על הנושא)
האלמנט
MappingFragment אין לו משמעות כרגע אבל יהיה לו משמעות בישויות המורכבות מכמה טבלאות - כלומר אם תבנו ישות שהמאפיינים שלהם מגיעים מכמה טבלאות (נקרא Entity Spliting) יהיה משמעות לאלמנט הזה
בתוכו יש מיפוי רגיל של מאפיין לעמודה.
בפרק הבא אנחנו נראה מה התרחש מאחורי הקלעים של הקוד המחולל בצורה אוטומטית ולאחר מכן נוכל להמשיך לבנות את המודל שלנו.
דברים שהזכרתי שנלמד בהמשך:
על כל האלמנטים של ה - Schema .
על קשרים בין ישויות.
על ירושה של ישויות.
הגדרת פונקציות למודל.
מאפיינים של EntityType.
נמשיך בלימוד על Entity Framework.
בפרק הזה נייצר את הישות הראשונה שלנו כמובן שנעשה זאת ידני - לאחר שנבין את הקוד וה - xml שנוצרים נוכל לייבא את שאר הטבלאות שלנו בצורה אוטומטית ורק לשנות את מה שצריך, במקום לייצר ידנית את הישויות.
בפרק הקודם אמרתי שנדגים את העבודה מול northwind - בסופו של דבר אני חושב שאני מעדיף לייצר DB כלשהו שדרכו אוכל לשקף את התובנות שלי על EF, ולא להשתמש ב - DB קיים שאני צריך לכתוב את הישויות לפי המבנה שלו.
נחזור בקצרה על התהליך שעשינו בפרק הקודם. תוכלו להוריד את ה - DB עבור הפרק הנוכחי
מכאן. או את הסקריפט שמייצר אותו
מכאן.
יש בו כרגע טבלה יחידה שנראת כך:
בהסתבסס על ה - DB הנוכחי עשינו את הפעולות הבאות:
יצרנו פרויקט בשם EntityFrameworkLab.
הוספנו מודל בשם EFModel
ייבאנו את טבלה בשם User.
מחקנו את היישות שנוצרה.
וצללנו לתוך ה - xml.
(כדי לראות את ה - xml אפשר לפתוח את קובץ ה - edmx בכל xml editor, אופצייה נוספת היא קליק ימין על קובץ ה - edmx לבחור ב - Open With ולבחור ב- Xml Editor.)
<!-- SSDL content -->
<edmx:StorageModels>
<Schema Namespace="EFModel.Store" Alias="Self"
Provider="System.Data.SqlClient"
ProviderManifestToken="2005"
xmlns:store="http://schemas.microsoft.com/ado/2007/12/edm/EntityStoreSchemaGenerator"
xmlns="http://schemas.microsoft.com/ado/2006/04/edm/ssdl">
<EntityContainer Name="EFModelStoreContainer">
<EntitySet Name="User" EntityType="EFModel.Store.User" store:Type="Tables" Schema="dbo" />
</EntityContainer>
<EntityType Name="User">
<Key>
<PropertyRef Name="Id" />
</Key>
<Property Name="Id" Type="bigint" Nullable="false" StoreGeneratedPattern="Identity" />
<Property Name="Name" Type="nvarchar" Nullable="false" MaxLength="50" />
<Property Name="PenName" Type="nvarchar" MaxLength="50" />
<Property Name="Picture" Type="image" />
<Property Name="Email" Type="varchar" Nullable="false" MaxLength="50" />
<Property Name="Gender" Type="int" Nullable="false" />
</EntityType>
</Schema>
</edmx:StorageModels>
בהתחלה יש הגדרות שונות של הסכמה ומאיפה מביאים את המידע.
מוגדר EntityContainer ו - EntitySet שממופה ל - EntityType בשם User
ויש את ההגדרה של טבלת ה - User אפשר לראות לדוגמא שעל עמודת Id יש מאפיין בשם StoreGeneratedPattern שמקבל Identity, מכיוון שב - DB זה מוגדר כעמודת Identity.
כעת אנחנו רוצים לייצר את הישות שתהיה ממופה לטבלה.
קליק ימין באמצע המסך נבחר ב - Add ואז ב - Entity
נקבל את המסך הבא:
ניתן לישות שלנו את השם User.
היות שאנחנו לא עובדים (כרגע) עם ירושה נשאיר את הערך None ב - BaseType.
ב - EntitySet ניתן את השם UserSet (כשנדבר על ירושות נרחיב בנושא של EntitySet)
כרגע עבור הבדיקות נוריד את הסימון מ - Create key property.
נלחץ על OK.
יש לנו ישות בשם User, בחלק העליון (Scalar) יהיו כל המאפיינים שממופים לעמודה עם ערך בדיד בבסיס הנתונים (שם, מייל וכו') בחלק התחתון (Navigation) יהיו קישורים לאובייקטים אחרים שקשורים לאובייקט User. (נדבר בפרקים הבאים)
ננסה לקמפל את הפרויקט ונקבל את שתי השגיאות הבאות:
Error 159: EntityType 'EFLabModel.User' has no key defined. Define the key for this EntityType.
Error 11007: Entity type 'User' is not mapped.
מסתבר שאי אפשר לייצר ישות או מאפיין) מבלי למפות אותה (וזה נקודה חשובה - כי אם נרצה להרחיב את הישות עם מאפיינים ממקורות אחרים לא נוכל ונצטרך להשתמש ב - partial, ועל כך בפרקים הבאים).
כמו כן כל ישות חייבת לפחות עמודה אחת שמוגדרת כ - EntityKey.
קליק ימין על הישות, לחיצה על Add ובחירה ב - Scalar Property.
נקבל מאפיין חדש עם השם property - נשנה כמובן את השם ל - Id.
נלחץ על F4 כדי לראות את המאפיינים של העמודה, ונראה את הרשימה הבאה:
Getter - איזה accessibility יהיה ל - Get (public, internal, private, protected
Setter - איזה accessibility יהיה ל - Set
Concurrency Mode - מה יקרה עם בזמן שנעבוד עם המאפיין מישהו נוסף ישנה את הערך של המאפיין (כלומר נניח שמדובר בעמודה של שם - כשנייבא את האובייקט מבסיס הנתונים הערך שלו יהיה "שלמה" אנחנו נשנה ל "יוסי" וכשננסה לשמור נגלה שהערך כרגע הוא "נועם" - כלומר מישהו שינה את הנתונים בזמן שעבדנו עליהם - המאפיין יקבע מה לעשות (כלום או להעיף שגיאה)
Entity Key - קובע האם מדובר במפתח של הישות.
Nullable - האם אפשר null
Type מאיזה סוג המאפיין (נדבר בפרקים הבאים על עוד סוגים שלא נמצאים ברשימה)
עבור המאפיין Id צריך לשנות את EntityKey ל - true
כעת נלך לחלק התחתון של העמוד (Mapping Details)
נבחר בטבלת User העמודה Id תמופה בצורה אוטומטית למאפיין Id.
כעת כשננסה לקמפל נקבל הודעת שגיאה:
Error 3023: Problem in Mapping Fragment starting at line 39: Column User.Name in table User must be mapped: It has no default value and is not nullable.
כלומר אנחנו חייבים למפות את כל העמודות שלא יכולים להיות null ואין להם ערך ברירת מחדל.
נמשיך להוסיף מאפיינים ולמפות - בסופו של דבר זה יראה בצורה הבאה:
כשנקמפל עכשיו זה יעבוד.
כעת נסתכל על המאפיין Gender - המאפיין הינו מסוג Int. די ברור לנו שבקוד המאפיין צריך להיות enum - הבעייה ש - EF (לפחות בגרסה הזאת) לא תומכת ב - enums.
ולכן הפיתרון היחיד
שמצאתי הוא - להגדיר את המאפיין Gender כ - private גם עבור ה - Getter וגם עבור ה - Setter ולהוסיף מאפיין חדש מסוג ה - enum שיגש אליהם.
נעשה זאת שלב אחרי שלב.
נשנה את השם של המאפיין Gender ל - InternalGender (לא חובה - מומלץ)
נגדיר את ה - Getter וה - Setter כ - private.
נוסיף class חדש בשם User
נגדיר אותו כ - partial.
ונכתוב את הקוד הבא
public enum UserGender
{
Male,
Female
}
public partial class User
{
public UserGender Gender
{
get
{
return (UserGender)this.InternalGender;
}
set
{
InternalGender = (int)value;
}
}
}
אמנם רציתי לעבור בפרק הזה גם על הקוד המחולל ועל ה - xml, אבל נראה לי שזה מספיק חומר, ולכן בפרק הבא נדבר על מאחורי הקלעים של מה שעשינו בפרק הזה.
מקווה שנהניתם
כמו שהבטחתי אנחנו נלמד כאן ביחד מהשלב הראשון איך לעבוד עם Entity Framework.
לצערי אני לא יכול להדגים כאן את העבודה מול ה - DB האמיתי שאני עובד מולו, לכן ההדגמה שלנו תהיה על בסיס נתונים ישן ואהוב בשם
Northwind.
אז מתחילים.
נפתח Visual Studio ונייצר פרויקט Asp.NET Web Application בשם EntityFrameworkLab.
קליק ימין על הפרויקט לחיצה על Add New Item ונבחר ב - ADO.NET Entity Data Model. נקרא למודל שלנו בשם NorthwindModel
אחרי ההוספה נקבל מסך שבו נצטרך לבחור האם לייצר מודל מתוך בסיס נתונים או לייצר מודל ריק.
אני חושב שמי שיכול לבחור בבניית מודל אוטומטי מתוך בסיס נתונים זה מרצה שרוצה להדגים את היכולות או מי שמכיר את הטכנולגיה מספיק טוב ויודע בדיוק איך המודל נבנה ואם צריך לשנות משהו הוא יודע איפה, אבל למי שלא מכיר את הטכנולוגיה מספיק טוב, מומלץ (לדעתי) לבחור במודל ריק (כי בחירה במודל מתוך בסיס נתונים - בהנחה שאנחנו מדברים על בסיס נתונים עם 13 טבלאות - תייצר מסך עמוס עם קשרים ומאפיינים וזה יכול קצת להבהיל).
כמובן שאני לא מצפה ממי שלא מכיר את הטכנולוגיה לדעת לבנות מודל - ולכן מה שאני עושה (לפחות בשלב הזה) הוא לייצר שני אפליקציות, אחת שאני בוחר בבניית המודל מתוך בסיס הנתונים ואחת (שעליה אני עובד) במודל ריק כשאני מתחיל לבנות לאט ובזהירות את המודל שלי כשאני מקפיד להבין כל דבר שאני עושה תוך כדי שאני מסתכל איך המודל האוטמוטי נוצר.
אז נבחר במודל הריק, ונלחץ על Finish.
כעת נחזור ל - Visual Studio ויהיו לנו ארבעה חלקים במסך:
צד ימין: Model Browser - החלון שבו נראה את הישויות מבסיס הנתונים ואת הישויות שמקושרים אליהם (נדבר עוד הרבה בהמשך על החלון הזה)
צד שמאל: ToolBox - נוכל לגרור פקדים רלוונטיים למסך.
למטה: Mapping Details - נראה את המיפוי בין הישויות מתוך בסיס הנתונים לבין הישויות של Entity Framework.
במרכז: נראה ציור גרפי של כל הישויות והקשרים ביניהם.
כעת אנחנו רוצים לייבא טבלה מתוך בסיס הנתונים למודל שלנו.
קליק ימין על ה - NorthwindModel.edmx ולחיצה על Update Model from Database
נבחר Connection String לבסיס הנתונים
ונקבל את החלון שאנחנו צריכים לבחור בו את הטבלאות שאנחנו רוצים לייבא - כרגע אני בוחר רק בטבלת Categories
לחיצה על Finish - תייבא את הטבלה ותייצר באופן אוטומטי את הישות עם ההתאמה המקיסימלית לטבלה. - כאמור אנחנו כרגע בתהליך למידה עצמאי של הטכנולוגיה ואנחנו רוצים להבין מה קורה מאחורי הקלעים ואיך זה עובד ומה כל מאפיין עושה - ולכן אנחנו נמחק את הישות שהוא יצר (בחירת היישות בחלון המרכזי ולחיצה על Delete).
כעת נסכם מה שיש לנו.
החלון המרכזי ריק.
החלון התחתון ריק.
בחלון הימני (Model Browser) יש קצת מידע (מיד נתמקד בו)
למעשה נסתכל על העץ של המודל ומיד נסתכל גם מה קרה מאחורי הקלעים (אני מאמין גדול בצלילה לעומק כדי לדעת איך לעבוד נכון עם הטכנולוגיה)
אז מה יש לנו:
תחת המודל יש לנו שני חלקים.
הראשון נקרא NorthwindModel שבו יש כמה חלקים - אבל היות שהם כרגע ריקים (כי מחקנו את הישות שנוצר לנו) נדבר עליהם עוד מעט השני נקרא NorthwindModel.Store שמכיל את הטבלאות מבסיס הנתונים - וכרגע יש לנו טבלה אחת שנקראת Categories עם כל העמודות שלה.
למעשה מאחורי הקלעים מתחלק לשניים.
אחד: הקוד האוטומטי שמחולל
שניים: ה - xml שנוצר.
אם נפתח את קובץ NorthwindModel.Designer.cs נראה את הקוד שחולל כתוצאה ממה שעשינו עד כה (וזה לא הרבה)
public partial class NorthwindEntities : ObjectContext
{
public NorthwindEntities() :
base("name=NorthwindEntities", "NorthwindEntities")
{
this.OnContextCreated();
}
public NorthwindEntities(string connectionString) :
base(connectionString, "NorthwindEntities")
{
this.OnContextCreated();
}
public NorthwindEntities(EntityConnection connection) :
base(connection, "NorthwindEntities")
{
this.OnContextCreated();
}
partial void OnContextCreated();
}
אין כרגע הרבהקוד - אנחנו רואים שיש class (שמוגדר כ - partial וזה חשוב כדי שנוכל בעתיד להרחיב את ה - class) בשם NorthwindEntities שאין בו כלום מעבר לכמה בנאים ומתודה בשם OnContextCreated שנקראת מהבנאים ומוגדרת גם כן כ - partial כך שאם נרצה להרחיב את המחלקה נוכל להשתמש בפונקצייה הזאת כדי לעשות דברים בזמן יצירת המודל.
כעת נסתכל על משהו יותר חשוב ב - Entity Freamwork - נסתכל על ה - xml.
למעשה כל מה שאנחנו עושים ב - designer מתואר בקובץ xml. נוכל להסתכל עליו במידה ונפתח את הקובץ עם הסיומת edmx ב - xml editor כלשהוא.
אז מה הוא מכיל ?
כשנפתח אותו נראה שהוא מחולק לשני חלקים
אחד בשם EF Runtime content
השני בשם EF Designer content
ה - Designer content לא מעניין אותנו בכלל, אנחנו מתעניינים רק ב - Runtime content (לפחות כרגע).
הוא עצמו מחולק לשלושה חלקים.
SSDL (Store Schema Definition Language) content - מתאר את הישויות מבסיס הנתונים (מה שהיה ב - Model Browesr תחת ה - Store)
CSDL (Conceptual Schema Definition Language) content - מתאר את הישויות של ה - Entity Framework (מה שיהיה ב - Model Browser תחת ה - Model)
C-S (Conceptual - Store) mapping content - מתאר את הקשר בין ה - SSDL לבין ה - CSDL, כלומר ממפה בין בסיס הנתונים לאובייקטים שלנו.
כרגע היות שאין לנו שום ישות - כל ה - xml ריק חוץ מה - SDDL שיש בו תיאור של טבלת Categories
1 <edmx:StorageModels>
2 <Schema Namespace="NorthwindModel.Store" Alias="Self"
3 Provider="System.Data.SqlClient" ProviderManifestToken="2005"
4 xmlns:store="http://schemas.microsoft.com/ado/2007/12/edm/EntityStoreSchemaGenerator"
5 xmlns="http://schemas.microsoft.com/ado/2006/04/edm/ssdl">
6 <EntityContainer Name="NorthwindModelStoreContainer">
7 <EntitySet Name="Categories"
8 EntityType="NorthwindModel.Store.Categories"
9 store:Type="Tables" Schema="dbo" />
10 </EntityContainer>
11 <EntityType Name="Categories">
12 <Key>
13 <PropertyRef Name="CategoryID" />
14 </Key>
15 <Property Name="CategoryID" Type="int" Nullable="false" StoreGeneratedPattern="Identity" />
16 <Property Name="CategoryName" Type="nvarchar" Nullable="false" MaxLength="15" />
17 <Property Name="Description" Type="ntext" />
18 <Property Name="Picture" Type="image" />
19 </EntityType>
20 </Schema>
21 </edmx:StorageModels>
נעבור על ה - xml במהירות.
שורה 1-5 מתאר הגדרות של סכמת ה - xml
שורה 6 מוגדר אובייקט מסוג Container שיכיל את כל האובייקטים מסוג EntitySet.
שורה 7-9 מוגדר אובייקט מסוג EntitySet שיש לו שם (בדרך כלל השם של הטבלה שאותה הוא מייצג) קישור ל - EntityType (שזה ייצוג המבנה של הטבלה) והגדרה מה הסוג של ה - Type בדרך כלל טבלה אך יכול להיות View.
שורה 11 מוגדר EntityType שמתאר את המבנה של טבלת Categories עם כל העמודות וה - Primary Key.
נדבר בפרקים הבאים עוד הרבה על המושגים EntityType ו - EntitySet (בעיקר כשנראה איך מייצרים מודל עם תמיכה ב OO - כלומר מודל עם ישויות שיורשים אחד מהשני)
לסיכום:
למדנו איך להתחיל פרוייקט חדש של Entity Framework.
למדנו על המסכים השונים ב - Visual Studio.
ראינו איך לייבא טבלאות מבסיס הנתונים למודל.
וראינו מה מתרנדר מאחורי הקלעים.
בפרק הבא:
נייצר את היישות הראשונה.
נתעמק בכל ההגדרות והמאפיינים השונים.
נראה מה מתרחש מאחורי הקלעים.
מקווה שנהניתם
וידוי קטן.
ברגע זה כלומר כשאני מתחיל לכתוב על EF אין לי שמץ של מושג בעבודה מול EF, יצא לי לשמוע פה ושם הרצאות (בעיקר ב - User Group וכד') על כמה טוב ונחמד לעבוד עם EF, אבל בפועל עד היום לא באמת יצא לי לעבוד עם הטכנולוגיה.
אני חייב להודות זה נשמע לי מגניב לגמרי - הרעיון שאני לא צריך להתעסק עם שאילתות ולתת למישהו לעשות את כל העבודה (למעשה גם לא עבדתי עם Linq To SQL).
אני מתחיל בימים אלו פרויקט חדש (פנימי) בסלע והדרישה שעלתה שם לעבוד עם EF. מבחינתי זה מעולה - אני תמיד שמח ללמוד טכנולוגיות חדשות (וטובות).
אבל היות שכאמור אין לי מושג איך לעבוד עם זה - אז אני צריך להתחיל ללמוד בעצמי - כלומר קריאה באינטרנט, לשאול אנשים שמכירים (ולמזלי בסלע יש הרבה אנשים שעומדית בחזית הטכנולוגיה) ובעיקר הרבה ניסוי ותהייה.
חשבתי לעצמי אם אני הולך לעבור בימים אלו סנדת למידה (עצמאית) על EF, אני יכול לכתוב סדרת פוסטים שתתחיל מנקודת האפס (כלומר לצאת מתוך הנחה שהקורא לא יודע כלום על EF) ולהתקדם לאט.
כדי שלא יקרה מצב שבו אכתוב שטויות על EF ורק בעוד חודש אני אגלה שלא היה משמעות למה שכתבתי - כל פוסט בסדרה יעבור תחת עינו של
עידו פלטו (חבר יקר ובלוגר מוערך) שהוא אחד מהמומחים של סלע ל - EF.
כאן תוכלו לקרוא מאמר שכתב עידו על Entity Framework.
גם אם לא הבנתם את כל המאמר (כאמור סקירה כללית של EF - ואני יוצא מתוך הנחה שיתכן והקורא עדיין לא מכיר EF - כמוני) לא נורא - אנחנו מתקדם יחד בסדרת הפוסטים עד שנהיה מומחים ל - EF.
בהצלחה (גם לי)
בפוסט
הזה הדגמתי איך ניתן להציג חלון מודלי לפני ריצה לשרת ורק במידה וחזר true מהחלון המודלי מתבצעת הריצה לשרת.
בדוגמא ההיא יש, FileUpload, CheckBoxList ולחצן.
בזמן לחיצה על Submit קופץ חלון מודלי שמבקש להכניס עבור כל שם שנבחר ב - CheckBixList את ה - ID שלו. לשאר הפרטים והדוגמאות עיינו בפוסט הקודם.
הבטחתי שאני אדגים גם מצב שבו יש ולידטור על הדף, לדוגמא: אנחנו רוצים לבדוק האם המשתמש העלה קובץ -
למעשה ב - html תהיה לנו תוספת פשוטה של:
<asp:RequiredFieldValidator ID="rfv" ValidationGroup="group1"
runat="server" ControlToValidate="fu" Text="*"></asp:RequiredFieldValidator>
כש: fu הוא ה - ID של ה - FileUpload וכמובן ללחצן יש גם ValidationGroup=group1.
אמנם הקוד מהפוסט הקודם יעבוד כמו שצריך - אבל החלון המודלי יקפוץ לפני הבדיקה האם יש קובץ. במידה ונרצה קודם את לבצע את הבדיקות נצטרך לשנות את מתודת CheckIds בצורה הבאה:
function CheckIds() {
var btnId = '<%= btn.ClientID %>';
var option = new WebForm_PostBackOptions(btnId, "", true, "group1", "", false, true);
if (Page_ClientValidate(option.validationGroup)) {
var users = GetAllSelectedUsers();
var returnValue = window.showModalDialog('MyPopupPage.aspx?users=' + users);
if (returnValue) {
WebForm_DoPostBackWithOptions(option);
}
}
return false;
}
מה יש לנו כאן:
מקבלים את ה - ID של הלחצן.
מייצרים את האובייקט של הולידטור.
מפעילים את הולידצייה.
במידה והצליח אנחנו ממשיכים בתהליך שהוסבר בפוסט הקודם עם בדיקת המספרים
קיצור מקשים מאוד נפוץ הוא ctrl + e + c ששם את השורה בהערה.
כנראה חלמתי או משהו ולחצתי על צירוף אחר, ופתאום אני רואה את זה:
התחלתי לחפש בכל מקום איך אפשר להסיר את הסימנים האלו (לא ממש מבין למה הפיצ'ר הזה)
בסוף עשיתי את הצעד המיואש והמתבקש הבא, לחצתי על ctrl + e ועברתי על כל מקש ומקש.
בסופו של דבר מצאתי שהצירוף הוא ctrl + e + s.
נניח שיש לנו טופס שהמשתמש ממלא בו פרטים כלשהם ובזמן לחיצה על OK לפני הריצה לשרת אנחנו רוצים להציג חלון אחר (מודלי) ובמידה והמשתמש ילחץ באותו חלון על OK נוכל לעשות ריצה לשרת בחלון הראשון .
המוטובצייה לעשות את זה יכולה להיות מכל מיני סיבות - אני אדגים כאן כדוגמא, בטופס הראשון משתמש אמור להעלות קובץ ולבחור משתמשים שיכולים לראות את הקובץ, בזמן לחיצה על OK נפתח לו חלון חדש שבו יש לו GRID עם השמות של המשתמשים ותיבת טקסט שבו הוא אמור להכניס את מספרי הזהות של המשתמשים שהוא בחר - ורק במידה ומספרי הזהות נכונים יוכל המשתמש לסגור את החלון המודלי על ידי לחצן OK ואז תהיה ריצה לשרת בחלון הראשון. (כמובן ויתכן פיתרונות טובים יותר מאשר חלון חדש עבור הדוגמא הזאת - המטרה של הפוסט היא להראות איך ניתן לעשות את זה כשחייבים בחלון חדש)
למעשה המימוש של זה מתחלק לשניים - האם יש ולידצייה כלשהי בחלון הראשון או לא - במידה ואין שום ולידצייה זה יהיה יותר פשוט אבל במידה שיש ולידצייה (לדוגמא שהמשתמש העלה קובץ) מן הסתם נרצה להפעיל את הולידצייה לפני פתיחת החלון המודלי של הכנסת מספרי הזהות.
בהתחלה אני אדגים את הפיתרון במצב שאין שום ולידצייה על העמוד.
ניתן להוריד את דוגמת הקוד
מכאן. (הדוגמא להורדה מכילה יותר דברים - כמו styles וכו' - כאן אני מציג רק את קטעי הקוד הרלוונטיים)
החלון הראשון:
1 <asp:FileUpload ID="fu" runat="server" />
2
3 <asp:CheckBoxList ID="chkUsers" runat="server">
4 <asp:ListItem Text="Shlomo" Value="Shlomo"></asp:ListItem>
5 <asp:ListItem Text="Noam" Value="Noam"></asp:ListItem>
6 <asp:ListItem Text="Yossi" Value="Yossi"></asp:ListItem>
7 <asp:ListItem Text="Tom" Value="Tom"></asp:ListItem>
8 <asp:ListItem Text="Ido" Value="Ido"></asp:ListItem>
9 </asp:CheckBoxList>
10
11 <asp:Button ID="btn" runat="server" Text="Submit"
12 OnClientClick="return CheckIds()" OnClick="btn_OnClick" />
כמו שאנחנו רואים יש UploadFile, רשימה של משתמשים ולחצן.
בזמן לחיצה לפני הריצה לשרת אנחנו מפעילים את פונקצית CheckIds בצד בלקוח.
function CheckIds() {
var users = GetAllSelectedUsers();
return window.showModalDialog('MyPopupPage.aspx?users=' + users);
}
בהתחלה אנחנו מקבלים רשימה של כל המשתמשים מהפקד (אני לא מדגים את הפונקציה - אפשר להוריד את הגרסה המלאה של התוכנית,
מכאן)
הפונקציה תחזיר true או false בהתבסס על מה שיוחזר מהחלון myPoupPage, ואנחנו שולחים לו כפרמטר את שמות המשתמשים. (כמובן יעבוד רק ב - IE)
הקוד של MyPopupPage:
<asp:GridView ID="grd" runat="server" AutoGenerateColumns="false">
<Columns>
<asp:BoundField DataField="Name" HeaderText="Name" />
<asp:TemplateField>
<ItemTemplate>
<asp:TextBox ID="txtID" runat="server"></asp:TextBox>
<asp:RequiredFieldValidator ID="rfv" runat="server"
ControlToValidate="txtID" ErrorMessage="*">
</asp:RequiredFieldValidator>
</ItemTemplate>
</asp:TemplateField>
</Columns>
</asp:GridView>
<asp:Button ID="btnOk" runat="server" Text="OK" OnClick="btnOk_OnClick" />
<input type="button" value="Cancel" onclick="window.returnValue = false; window.close();" />
זה מה יש לנו:
הגדרה של גריד עם עמודה אחד שמקושרת ל - Name
ועמודה שנייה שיש בה תיבת טקסט ו - RequiredValidator
לחצן OK שרת לשרת לבדוק האם המספרים בסדר.
ולחצן Cancel שמחזיר false וסוגר את החלון.
חשוב מאוד להוסיף ב - head את הדבר הבא:
אחרת אחרי לחיצה על OK יפתח חלון חדש ולא מודלי - כמו שכתבתי
כאן על הבעייה הידועה.
צד השרת (של החלון המודלי)
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
DataTable table = new DataTable();
table.Columns.Add("Name");
table.Columns.Add("Id");
table.PrimaryKey = new DataColumn[] { table.Columns["Id"] };
if (Request["users"].Contains("Shlomo"))
table.Rows.Add("Shlomo", "1");
if (Request["users"].Contains("Noam"))
table.Rows.Add("Noam", "2");
if (Request["users"].Contains("Yossi"))
table.Rows.Add("Yossi", "3");
if (Request["users"].Contains("Tom"))
table.Rows.Add("Tom", "4");
if (Request["users"].Contains("Ido"))
table.Rows.Add("Ido", "5");
Session["table"] = table;
grd.DataSource = table;
grd.DataBind();
}
}
בפעם הראשונה אנחנו שומרים את כל האנשים ב - Session (כמובן בדוגמא זה hard code) ומקשרים את הגריד.
בלחיצה על OK - קורה הדבר הבא:
protected void btnOk_OnClick(object sender, EventArgs e)
{
DataTable table = (DataTable)Session["table"];
for (int i = 0; i < grd.Rows.Count; i++)
{
string name = grd.Rows[i].Cells[0].Text;
string id = ((TextBox)grd.Rows[i].FindControl("txtID")).Text;
DataRow row = table.Rows.Find(id);
if (row == null || row["Name"].ToString() != name)
{
string msg = string.Format("id {0} is not valid for {1}", id, name);
Page.ClientScript.RegisterClientScriptBlock(GetType(), "aaa", "alert('" + msg + "')", true);
return;
}
}
string script = "window.returnValue = true; window.close();";
Page.ClientScript.RegisterClientScriptBlock(GetType(), "key", script, true);
}
נרוץ על כל השורות בגריד, עבור כל שורה נוציא את השם והמספר.
נחפש לפי המספר האם הוא קיים בטבלה - במידה ולא מצאנו או שמצאנו אבל זה לא תואם לשם, נציג למשתמש הודעה.
במידה והכל בסדר נסגור את החלון ונגדיר את ה - returnValue כ - true.
היות ואין לי זמן כרגע - אפרסם את הפיתרון במידה שיש ולידצייה על הדף הראשון בפוסט הבא.
כולנו מכירים שלכל מערך יש Indexer, ומשתמשים בהם בצורה הבאה:
int[] intArr = { 2, 4, 7, 8, 6 };
int i = intArr[2];
אבל אנחנו מקבלים אובייקט מסוג Array נצטרך להשתמש במתודת GetValue ומתודת SetValue. (שזה מעצבן)
למעשה יש ל - Array את ה - Indexer היות ש - Array מממש את IList, מה שנצטרך לעשות זה את הדבר הבא:
Array arr = GetArray();
IList list = arr;
Console.WriteLine(list[2]);
מי שלא היה בהרצאה ב - IDCC ולא מעוניין לשמוע את ההרצאה מההקלטה.
מוזמן לבוא ל - User Group שבוע הבא ב - 21 לאוקטובר.
החלק הראשון במפגש יועבר על ידי
פבל ויתמקד ב - First Look at Visual Studio 2010
החלק השני יתמקד ב - First Look at C# 4.0 ויועבר על ידי.
בתפוז נשאלה שאלה: איך ניתן לחשוף אירועים של פקדים שנמצאים בתוך UserControl כדי שיוכלו להירשם אליהם מתוך הדפים.
אפשר לחשוב האם זה נכון לעשות את זה או לא, אבל במידה ומגיעים למסקנה שצריך לעשות את זה - עושים את זה בצורה הבאה:
נניח שיש לנו UserControl שיש לו לחצן ואנחנו רוצים לחשוף את אירוע OnClick שלו כדי שיוכלו להירשם אליו.
ה - UserControl יראה כך:
<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="WebUserControl1.ascx.cs"
Inherits="WebApplication32.WebUserControl1" %>
<asp:Button ID="Button1" runat="server" Text="Button" />
צורה אחת היא להגדיר את הלחצן כ - Public ואז יהיה ניתן להירשם לאירועים שלו מקוד - אני חושב שהדרך הזאת אינה נכונה כי גם אם מגיעים למסקנה שרוצים לחשוף אירוע - לא מומלץ לחשוף את כל האובייקט, תמיד כדאי לחשוף רק מה שצריך, ולכן נכתוב את הקוד הבא (ב - UserControl)
public event EventHandler ButtonOnClick
{
add
{
Button1.Click += value;
}
remove
{
Button1.Click -= value;
}
}
הקוד הזה הינו למעשה חשיפה של האירוע (כמו משתנה ומאפיין).
כעת בדף שבו גוררים את ה - UserControl אפשר לכתוב את הקוד הבא:
<div>
<uc1:WebUserControl1 ID="WebUserControl11" runat="server"
OnButtonOnClick="ButtonOnClick_Click" />
</div>
כשלמעשה המאפיין OnButtonOnClick הוא האירוע החדש שיצרנו עבור ה - UserControl
אפשר להוריד את דוגמת הקוד
מכאן:
נניח שיש לכם שני תיבות טקסט שהערכים שלהם הם תאריכים ואתם רוצים לוודא שאחד גדול מהשני (לדוגמא: From ו - To) - הערכים לתיבות הטקסט יכולים להגיע מכל מקור - או מהאובייקט Calendar של asp או מהאובייקט Calendar של ajax או מהאובייקט של טלריק - זה לא ממש משנה - הנקודה היא שאנחנו רוצים לבדוק שני תאריכים.
במחשבה ראשונה נשתמש ב - CompareValidator המובנה וזה יראה כך:
<asp:TextBox ID="TextBox1" runat="server"></asp:TextBox>
<asp:TextBox ID="TextBox2" runat="server"></asp:TextBox>
<asp:Button ID="Button1" runat="server" Text="Button" ValidationGroup="dates" />
<asp:CompareValidator ID="CompareValidator1" runat="server" ErrorMessage="CompareValidator"
ControlToCompare="TextBox2" ControlToValidate="TextBox1" Operator="GreaterThan"
Type="Date" ValidationGroup="dates"></asp:CompareValidator>
ל - CompareValidator יש מאפיין בשם Type שאחד מהערכים שאפשר להכניס הוא Date ואז לכאורה המערכת אמורה לבדוק שהתאריך בתיבת הטקסט הראשונה גדול מהתאריך בתיבת הטקסט השנייה.
וזה אמנם עובד, אבל רק בתנאי שהתאריכים הם רק תאריכים ה - validator לא יודע לבדוק שעות או תאריכים שיש בהם שעות - קצת מטופש לדעתי שהם לא השקיעו עוד שעת פיתוח כדי להוסיף בדיקה גם על שעות.
אחרי דיבוג של הסקריפטים של מייקרוסופט (זה שווה פוסט נפרד איך עושים את זה) וחיפוש קצר בגוגל מצאתי
מישהו שנתן סוג של פיתרון - הוא משתמש שם ב - CustomValidator ובודק בעצמו, הרעיון די נחמד אבל העדפתי לשכלל אותו ולהשתמש ב - CompareValidator.
למעשה כדי לתקן את ההתנהגות של ה - CompareValidator אנחנו צריכים לחשוב גם על צד הלקוח (כלומר לשנות את ההתנהגות של הסקריפטים שרצים אצל הלקוח) וגם על צד השרת (כלומר לשנות את הבדיקה בצד השרת).
צד הלקוח:
מה שאנחנו נעשה כאן יהיה לשנות את המימוש של הפונקציה שבודקת את התאריכים.
צד השרת:
נוכל לבחור לרשת מה - CustomValidator או לדרוס את המתודה Validate ולעשות את הבדיקה לבד - (אני אדגים את שניהם).
מימוש:
צד הלקוח:
נוסיף ל - Validator מאפיין שנקרא לו IsDateTime (ב - html הרי מותר להוסיף כל מאפיין לכל תג)
<asp:CompareValidator ID="CompareValidator1" runat="server" ErrorMessage="CompareValidator"
ControlToCompare="TextBox2" ControlToValidate="TextBox1" Operator="GreaterThan" IsDateTime="true"
Type="Date" ValidationGroup="dates"></asp:CompareValidator>
במידה ונחליט בצד השרת לרשת מה - Validator מן הסתם נגדיר את המאפיין הזה. ואז זה יראה כך:
<cc1:DateTimeValidator ID="CompareValidator1" runat="server" ErrorMessage="CompareValidator"
ControlToCompare="TextBox2" ControlToValidate="TextBox1" Operator="GreaterThan"
IsDateTime="true" Type="Date" ValidationGroup="dates"></cc1:DateTimeValidator>
כשכמובן נוסיף בראש הדף את הרישום ל Control
<%@ Register Assembly="[Assembly]" Namespace="[Namesapce]" TagPrefix="cc1" %>
כעת נוסיף את הסקריפט הבא: (הסברים למטה)
<script type="text/javascript">
var OriginalValidatorCompare;
function ChangeValidatorCompare() {
if (typeof (ValidatorCompare) == "function") {
OriginalValidatorCompare = ValidatorCompare;
ValidatorCompare = function(operand1, operand2, operator, val) {
if (val.IsDateTime == undefined || val.IsDateTime.toLowerCase() != "true") {
return OriginalValidatorCompare(operand1, operand2, operator, val);
}
var result = OriginalValidatorCompare(operand1.substr(0, operand1.indexOf(' ', 0)),
operand2.substr(0, operand2.indexOf(' ', 0)),
operator, val);
if (result == false) {
return result;
}
var op1, op2;
var hour1 = operand1.substring(11, 13);
var minuts1 = operand1.substring(14, 16);
var secound1 = operand1.substring(17, 19);
var hour2 = operand2.substring(11, 13);
var minuts2 = operand2.substring(14, 16);
var secound2 = operand2.substring(17, 19);
op1 = new Date(0, 0, 0, hour1, minuts1, secound1);
op2 = new Date(0, 0, 0, hour2, minuts2, secound2);
switch (operator) {
case "NotEqual":
return (op1 != op2);
case "GreaterThan":
return (op1 > op2);
case "GreaterThanEqual":
return (op1 >= op2);
case "LessThan":
return (op1 < op2);
case "LessThanEqual":
return (op1 <= op2);
default:
return (op1 == op2);
}
}
}
}
</script>
ב - onload של הדף נפעיל את הפונקצייה ChangeValidatorCompare
מה שאנחנו עושים בפונקציה - זה למעשה לשנות את המימוש של הפונקציה המקורית.
יש לנו משתנה בשם OriginalValidatorCompare שאמור לשמור לנו מצביע לפונקציה המקורית.
כשמפעילים את ChangeValidatorCompare (בזמן טעינת הדף) אנחנו בודקים האם יש פונקציה בשם ValidatorCompare, במידה וכן אנחנו שומרים מצביע לפונקציה ומגדירים שמעכשיו קריאה ל - ValidatorCompare תפעיל פונקציה חדשה.
ברגע שמישהו ילחץ על הלחצן יופעלו הסקריפטים שאמורים לבדוק.
בתוך הסקריפט של מיקרוסופט יש קריאה לפונקציה ValidatorCompare - וכאמור זה יפעיל את הפונקציה שלנו.
בהתחלה אנחנו בודקים האם יש הגדרה ב - validator של IsDateTime והאם הוא מוגדר כ - true
במידה ולא נחזיר את התוצאה של הפונקציה המקורית.
במידה וכן - אנחנו נפעיל את קטע הקוד הבא:
var result = OriginalValidatorCompare(operand1.substr(0, operand1.indexOf(' ', 0)),
operand2.substr(0, operand2.indexOf(' ', 0)),
operator, val);
קוראים לפונקציה המקורית - אבל שולחים רק את התאריך (על ידי חיפוש במחרוזת שקבלנו את תו הרווח הראשון).
במידה וחזר false כמובן שנחזיר false מכיון שאין טעם לבדוק את השעות אם התאריכם לא עמדו בבדיקה.
במידה וחזר true - נבדוק את השעות
var hour1 = operand1.substring(11, 13);
var minuts1 = operand1.substring(14, 16);
var secound1 = operand1.substring(17, 19);
var hour2 = operand2.substring(11, 13);
var minuts2 = operand2.substring(14, 16);
var secound2 = operand2.substring(17, 19);
op1 = new Date(0, 0, 0, hour1, minuts1, secound1);
op2 = new Date(0, 0, 0, hour2, minuts2, secound2);
נוציא את השעות דקות ושניות מהתאריכים שקבלנו - ונייצר שני אובייקטים מסוג Date.
כעת נשאר רק לעשות את הבדיקה
switch (operator) {
case "NotEqual":
return (op1 != op2);
case "GreaterThan":
return (op1 > op2);
case "GreaterThanEqual":
return (op1 >= op2);
case "LessThan":
return (op1 < op2);
case "LessThanEqual":
return (op1 <= op2);
default:
return (op1 == op2);
}
זה למעשה מסכם לנו את צד הלקוח.
מימוש צד השרת:
public class DateTimeValidator : CompareValidator
{
public bool IsDateTime { get; set; }
protected override bool EvaluateIsValid()
{
if (!IsDateTime)
{
return base.EvaluateIsValid();
}
DateTime op1 = DateTime.Parse(((TextBox)FindControl(ControlToValidate)).Text);
DateTime op2 = DateTime.Parse(((TextBox)FindControl(ControlToCompare)).Text);
switch (Operator)
{
case ValidationCompareOperator.Equal:
return op1 == op2;
case ValidationCompareOperator.GreaterThan:
return op1 > op2;
case ValidationCompareOperator.GreaterThanEqual:
return op1 >= op2;
case ValidationCompareOperator.LessThan:
return op1 < op2;
case ValidationCompareOperator.LessThanEqual:
return op1 <= op2;
case ValidationCompareOperator.NotEqual:
return op1 != op2;
default:
return op1 == op2;
}
}
protected override void AddAttributesToRender(HtmlTextWriter writer)
{
writer.AddAttribute("IsDateTime", IsDateTime.ToString());
base.AddAttributesToRender(writer);
}
}
ההסבר הוא כמובן מאוד פשוט:
הוספנו מאפיין בשם IsDateTime במתודה AddAttributeToRender דאגנו להסיף אותה ל - html
ובמתודה EvaluateIsValid מימשנו את הבדיקה.
במידה והיינו מחליטים לא לרשת מ - CompareValidator היינו צריכים לדרוס את המתודה Validate של הדף ולמצוא את ה - validator המתאים בתוך המאפיין Validators של הדף (שמכיל את כל ה - validators) ולהחליט האם המאפיין IsValid של ה - validator הוא true או false בהתאם לבדיקה שעשינו.
בתפוז נשאלה שאלה:
מה הקוד שצריך לכתוב כדי להוריד קובץ בעזרת לינק פשוט ?
אני נתקל הרבה פעמים בשאלה הזאת - ולכן אני כותב כאן את דוגמת הקוד.
צריך לייצר handler ולכתוב ב - ProcessRequest את הקוד הבא:
public void ProcessRequest(HttpContext context)
{
string fileName = context.Request.QueryString["filename"];
FileInfo fi = new FileInfo(fileName);
context.Response.ContentType = "application/x-rar-compressed";
context.Response.AppendHeader("Content-Disposition",
string.Format("attachment; filename=download{0}", fi.Name));
context.Response.WriteFile(fileName);
context.Response.End();
}
כמובן שה - ContentType צריך להיות לפי סוג הקובץ (אפשר לבדוק בעזרת המאפיין Extension של FileInfo.
קוד ה - html
<a href="MyHandler.ashx?filename=C:\MyFile.rar">Download</a>