מיקרוסופט הודיעה היום על השקת ASP.NET WebMatrix – סביבת פיתוח וריצה חדשה לאתרי אינטרנט. כמובן שיש מ-ל-א פוסטים בנושא, והמטרה בפוסט הזה היא לסכם את הנקודות העיקריות במקום אחד ובנוחות.
ASP.NET WebMatrix הוא מוצר די גדול, שמורכב משני חלקים עיקריים: החלק הראשון, הוא סביבת הריצה עצמה. החלק השני, הוא סביבת הפיתוח (שאמנם מומלצת לשימוש, אבל לא חובה לשימוש).
סביבת הריצה של ASP.NET WebMatrix
סביבת הריצה של ASP.NET WebMatrix מורכבת ממספר חלקים עיקריים:
ASP.NET Web Pages
דרך חדשה לפיתוח אפליקציות ASP.NET ששונה משמעותית מ ASP.NET WebForms הוותיקה וגם מ ASP.NET MVC הצעירה. מדובר בפיתוח המיועד להיות כמה שיותר פשוט – פיתוח שמכיל קוד HTML ו inline server code שכתוב ב ASP.NET. דומה לצורה המוכרת מפיתוח PHP ומפיתוח ASP.
מנוע הרינדור Razor
Razor הוא מנוע תצוגה חדש שמשולב ב WebMatrix ומולו עובדים Web Pages בד"כ (על אף שלא חובה). בגדול, מדובר במנוע רינדור שאמור להציע syntax נוח יותר לפיתוח inline code. הוא גם זמין לשימוש ב MVC View.
SQL Server Comact Edition
מדובר למעשה בגרסאת אמבדד לדטאבייס המוכר. לא דורשת שום התקנה, עובדת באמצעות קובץ בלבד. פיתוח ב WebMatrix קל במיוחד איתה, אבל ניתן להשתמש בה גם לצרכים רבים אחרים. דרך אגב, יש תמיכה מלאה בעבודה עם PHP מול SQL Server ומול SQL Server CE באמצעות דרייבר שמיקרוסופט פיתחו.
סביבת הפיתוח של ASP.NET WebMatrix
סביבת הפיתוח של ASP.NET WebMatrix היא סביבה חדשה ויפה שמאפשרת לפתח בקלות מול סביבת הריצה של ASP.NET WebMatrix (על אף שניתן לפתח אליה גם באמצעות notepad ואני מניח שמתישהו בקרוב גם יהיה פלאגין שיהפוך את הפיתוח מ Visual Studio לקל יותר). בנוסף, היא תומכת בפיתוח PHP.
ניתן לפתח אתר מאפס, או לחלופין – להתחיל מקוד של פרוייקט אופן סורס (מתוך גלרייה די רחבה ,לא צריך להוריד בנפרד) ולעשות את מה שצריך.
השילוב של DB הוא גם פשוט למדי, ניתן בקלות להוסיף דטאבייס של SQL CE ובאמצעות איזשהו פרימוורק קטן ניתן בקלות בראש עמוד לפתוח connection ל DB עם סינטקס פשוט, להגיד איזה שאילתה אתה רוצה, ובעמוד לשלב איטרציות על הנתונים שחזרו וכו'.
כמובן שיש אפשרות להשתמש גם ב Membership של ASP.NET לניהול אוטנטיקציה... ולמעשה, בתיאוריה – אפשר להשתמש בהכל.
Deployment ל ASP.NET WebMatrix
להפיץ את האתר שפיתחתם באמצעות ASP.NET WebMatrix זה ממש כסף קטן. תרתי משמע. גם מאד פשוט להפיץ את האתר וגם יש רשימה של הוסטרים שמציעים דילים שונים לאכסון האתר בחינם. למשל, Applied Innovations.
התקנת ASP.NET WebMatrix
התקנת ה ASP.NET WebMatrix מתבצעת כחלק מה Platform Installer 3 Beta. לא מאד קשה – פשוט תיכנסו לפה ותעקבו אחרי ההוראות.
ומה הבעיות בזה?
אני מניח שהבעייה העיקרית היא שלא ממש ברור לי למי זה מיועד – אני לא רואה את עצמי או כל מפתח מקצועי אחר משתמש בזה (וגם מיקרוסופט לא רואים את זה קורה, לדעתי). ואני מתכוון גם לכלי הפיתוח (שהוא אמנם חביב, אבל לא הרבה מעבר לזה) וגם לקונספט של ASP.NET Web Pages שמחזיר אותנו לימים העליזים והמבולגנים במקצת של ASP3. רק לראות בכל הדוגמאות קוד בראש העמוד קטע קוד שנעשה ארוך יותר ככל שהדוגמא מתקדמת יותר שמכיל עירוב משונה בין DAL ל-BLL (ויותר גרוע – כשזה מופיע תוך כדי הקוד...) גורם לי לרתיעה קלה. ובכלל, אפילו אם כותבים את זה איכשהו מסודר (מה שעלול להיות קצת בעייתי כי הסביבת פיתוח עצמה מעודדת לכתוב בצורה לא ממש מסודרת והדוגמאות מלמדות כתיבת קוד זוועתי) השיטה של inline code נראית לי כמו נסיגה לימי עבר.
למה זה כן טוב? מי שמתחיל בפיתוח ורוצה תוצאות מהירות. מי שצריך לעשות התאמה לאיזשהו פרוייקט אופן סורס ברמה שלא דורשת התעסקות פיתוחית, אלא התעסקות יותר ברמה העיצובית וברמת התצוגה. דברים בסגנון הזה. לי אישית, אין יותר מדי כוונה להשתמש בכלי הזה.
אם מדובר על בעיות בכלים עצמם – אז הבעיות הרציניות שנתקלתי בהן זה ההיעדר לכאורה של אינטליסנס וההעדר של אינדנטציה אוטומטית. 2 דברים מאד מאד חשובים לי באופן אישי שנראה שהושמטו מהעורך קוד. שאר הדברים מספקים את מה שהובטח.
מקורות לקריאה נוספת
ולסיום: אם תהיתם מאיפה מוכר לכם השם WebMatrix – אז מדובר בכלי פיתוח של מיקרוסופט שהיה קיים בעבר ואיפשר פיתוח ASP.NET 1.1 בחינם. משהו מקביל ל Visual Web Developer כיום.
תהנו.
ב 28.03 העברתי הרצאה בכנס של סטארטאפסידס בנושא “איך להרוס את Windows 7”. רוב קוראי הבלוג הזה בטח יודעים שבד”כ אני מפתח בטכנולוגיות מיקרוסופט, ובכלל די מפרגן למוצרי מיקרוסופט. אבל מה לעשות – לפעמים להרוס מוצר מעניין לא פחות מלפתח אליו :-)
בהרצאה יש כמה דרכים רציניות (וגם כמה הומוריסטיות, בהתחלה) לאיך להרוס Windows 7 (כמובן שבאמצעות קצת רצון טוב ודיסק התקנה של Windows 7, ניתן לפתור הכל) בין השאר – הסרת Windows מרשימת האתחול של Windows, פגיעה בקבצי מערכת ועוד כמה שיטות נחמדות. האורך של ההרצאה זה 14 דקות.
לצפייה ביוטיוב (מומלץ לצפות ב HD ובמסך מלא – ככה יותר ברורות ההדגמות שמוצגות על המסך).
להורדת המצגת עם הסרטונים (מובנים במצגת).
שחר.
מה זה SMO?
SMO, ר”ת של SQL Server Management Object, זה אוסך של אובייקטים החושפים פונקציונאליות של ניהול שרת SQL Server על רכיביו השונים ובכל הרמות (ניהול שרת, ניהול דטאבייסים, ניהול הרכיבים בדטאבייס מסויים וכו’).
מדובר, למעשה, במקבילה דוט.נט-ית לרכיב שידוע בשם SQL-DMO (עוד ד”ת - SQL Distributed Management Objects).
האסמבליס (קבצי הDLL) של SMO נמצאים בנתיב C:\Program Files\Microsoft SQL Server\100\SDK\Assemblies\Microsoft.SqlServer.Smo.dll (מדובר בקבצים של SQL SERVER 2008, כאשר c:\Program Files זה כמובן תיקיית האפליקציות שלכם).
המטרה
המטרה בפוסט זה היא להדגים דרך לכתוב מתודה שמייצרת קובץ המכיל משפטי SQL היכולים לייצר טבלאות שונות. פונקציונאליות דומה לפונקציונאליות המתקבלת ב SQL Server Management Studio כשלוחצים קליק ימני על DB ובוחרים ב Generate Scripts.
השיטה
תחילה, נצטרך להוסיף רפרנס לחברים הבאים:
- Microsoft.SqlServer.ConnectionInfo
- Microsoft.SqlServer.Management.Sdk.Sfc
- Microsoft.SqlServer.Smo
[האסמבליס שלהם נמצאים בתיקייה שציינתי, באותו שם, פשוט בתוספת .dll]
בדוגמא הבאה, אנחנו מעוניינים לגבות מספר טבלאות ספיציפיות, ששמם ידוע מראש הנמצאים ב DB ששמו ופרטי הגישה אליו ידועים מראש.
ככה תיראה המתודה שלנו:
1: public static void Backup(string FileName, string[] Tables)
2: {
3: StringBuilder sb = new StringBuilder();
4: Server srv = new Server(new Microsoft.SqlServer.Management.Common.ServerConnection("<db name>", "<user name>", "<password>"));
5: Database dbs = srv.Databases["<db name>"];
6: ScriptingOptions options = new ScriptingOptions();
7: options.ScriptData = true;
8: options.ScriptDrops = false;
9: options.FileName = FileName;
10: options.EnforceScriptingOptions = true;
11: options.ScriptSchema = true;
12: options.IncludeHeaders = true;
12: options.AppendToFile = true;
13: options.Indexes = true;
14: options.WithDependencies = true;
15: foreach (var tbl in Tables)
16: {
17: dbs.Tables[tbl].EnumScript(options);
18: }
19: }
המתודה שלנו מקבלת במקרה הזה שני פרמטרים, הראשון הוא שם הקובץ שאותו אנחנו רוצים לייצא והשני זה מערך עם שמות הטבלאות.
בשורה 4 במתודה מתחיל הדבר המעניין. אנחנו יוצרים מופע של אובייקט בשם Server המייצג, כמה מפתיע, את שרת ה SQL עצמו ומתחברים באמצעות השם משתמש והסיסמא שלנו.
בשורה 5, אנחנו בוררים מתוך כל ה DB’s שבשרת את זה שמעניין אותנו. שימו לב, שלמרות שבקוד הזה לא דאגתי לנושא כלל, חובה להתייחס ל exceptions שיכולים לעוף בדרך. שרת לא זמין, שמות משתמש וסיסמא לא תקינים, DB שאין הרשאות אליו, DB שלא קיים – כל אלה הם פוטנציאל לבעיות.
בשורות הבאות, אנחנו מגדירים סט של הגדרות. המעניינות שבהם זה שאנחנו מגדירים לו לייצא את הנתונים עצמם (ScriptData) בנוסף לסכימה (ScriptSchema) וכמובן שאנחנו מעוניינים באינדקסים ובתלויות אחרות (כשאנחנו מגדירים WithDependencies = true, זה אומר שאם יש איזשהו FK לטבלה אחרת, אז הטבלה ההיא תיווצר גם. אם לא היינו מגדירים את זה, היה נוצר רק מה שאנחנו מבקשים – שימו לב, שבמצב כזה תיתכן יצירה כפולה של טבלאות, לא טיפלתי בזה במקרה הזה – אבל צריך לשים לב, או לחלופין להגדיר ל false ובאופן ידני לוודא שמעבירים את השמות של הטבלאות הנחוצות).
הסבר על ההגדרות הקיימות אפשר למצוא כאן.
שורות 15-18 הן המעניינות – אנחנו עוברים על כל הטבלאות במערך שהועבר, ועבור כל טבלה קוראים למתודה Table.EnumScript שעובדת לפי ההגדרות שמועברות אליה. המתודה הזאת מחזירה, בנוסף, גם collection של סטרינגים המכיל למעשה את ה משפטי SQL.
במקרה הזה, אני לא מטפל ולא קולט את הערך המוחזר, כי הגדרתי בכל אופן FileName, כלומר המתודה מייצאת את המידע לקובץ (מכיוון שאנחנו עובדים עם מספר מרובה של טבלאות, כלומר מספר רב של פעמים תיקרא EnumScript, חשוב גם להגדיר AppendToFile = true).
למה EnumScript ולא Script?
מי שיסתכל בתיעוד, או סתם יכתוב בעצמו ויסתכל באינטיליסנס, יראה שקיימת גם מתודה בשם Script (שמגיע בכלל בירושה מ TableViewTableTypeBase.Script) אז למה לא להשתמש בה?
מסתבר (וזה לא ממש מתועד), שאם מגדירים ScriptData = true, חובה להשתמש ב EnumScript כי Script תזרוק, במידה שנשתמש בה, Exception שאומר This method does not support scripting data. אז לכן, אנחנו משתמשים ב EnumScript במקום.
סיכום
לצורך עבודה עם SQL Server מאפליקציות דוט נט קיים רכיב בשם SMO.
ניתן לייצר משפטי SQL המשמשים ליצירת טבלאות והכנסת המידע אליהן עם המתודה Table.EnumScript כאשר את ההגדרות מעבירים עם אובייקט ScriptingOptions.
למי שמתעניין בנושא, מתודות בעלות שם דומה קיימות גם לאובייקט Databse ולאובייקטים של ישויות DB נוספות. קיימת גם מחלקה בשם Scripter המשמשת (גם לעיתים מאחורי הקלעים) ליצירת סקריפטים שונים של SQL מרכיבי DB קיימים.
בהצלחה.
נניח שיש לכם טבלה, עם עמודה הכוללת מספרים שלמים (int).באפליקציה שלכם, שלצורך העניין, משתמשת נניח ב LINQ to SQL או ADO.NET Entity Framework, אתם רוצים לקבל את הממוצע של העמודה הזאת. אתם אומרים לעצמכם שזה לא אמור להיות מורכב בכלל. יש Extension Method לאגרגרציה של נתונים, אחת מהם היא Average.
אתם כותבים אפליקציה פשוטה שמריצה אותה ואמורה לעשות את הממוצע של המספרים 1,1,2. התוצאה המצופה היא אחד ושליש. אתם מקבלים אחד. למה?
ההסבר פשוט מאד: משפט ה SQL שנוצר כתוצאה משימוש ב Average משתמש בפונקציית T-SQL בשם AVG. מישהו במיקרוסופט החליט, שכשמפעילים AVG על עמודה שה type שלה הוא int, אז הערך המוחזר יהיה גם הוא int. החלטה מעניינת, בהתחשב בעובדה שזה אומר שבמספר די רחב של מקרים Average לא תחזיר את הממוצע – אבל אני לא מתיימר להבין את (חוסר) ההיגיון מאחורי זה.
לכן, אפילו אם הערך שאתם רוצים לאכסן בעמודה הוא מספר שלם בהכרח, אבל רוצים לקבל את הממוצע האמיתי – כדאי שתגדירו את העמודה ל real.
בהצלחה.
בפוסט הקודם הצגתי את LINQ2DQLDTOCreator, אפליקציה קטנה שמייצרת Data Transfer Objects על בסיס LINQ to SQL Data Classes. היום ביצעתי קצת שיפורים באפליקציה והרחבתי אותה כך שהיא תייצר DTO’s גם ממודל של ADO.NET Entity Framework.
האפליקציה הנוכחית כוללת את כל היכולות של האפליקציה הקודמת. כלומר, עדיין ניתן לייצר DTO’s על בסיס LINQ to SQL Data Classes. אבל, עכשיו האפליקציה גם תייצר DTO’s על בסיס entities, כלומר מחלקות עם EdmEntityTypeAttribute.
בנוסף, כאשר עובדים מול ADO.NET Entity Framework האפליקציה גם מתייחסת ל relationships בין entities שונים. כלומר, אם במודל מוגדר של entityיש קשר של יחיד ליחיד עם ישות אחרת, הדבר ייוצג בם ב DTO (כמובן, באמצעות קשר ל-DTO הרלוונטי). גם קשר של יחיד לרבים ייוצג. למשל, הנה ה DTO שנוצר עבור הישות Products מהמסד נתונים Northwind:
using System;
using System.Data.Linq;
using System.Linq;
using System.Collections.Generic;
/*
This Data Transfer Object created Automatically by DTOGenerator.
You can download DTOGenerator and get support in the project site: http://dtogenerator.codeplex.com
DTOGenerator developed by Shahar Gvirtz (http://weblogs.asp.net/shahar)
*/
namespace DTO
{
public class ProductDTO
{
public static ProductDTO GetDTOFromDALObject(ConsoleApplication3.Product src, bool GetChilds)
{
ProductDTO obj = new ProductDTO();
obj.Discontinued = src.Discontinued;
obj.ProductID = src.ProductID;
obj.ProductName = src.ProductName;
obj.QuantityPerUnit = src.QuantityPerUnit;
obj.ReorderLevel = src.ReorderLevel;
obj.UnitPrice = src.UnitPrice;
obj.UnitsInStock = src.UnitsInStock;
obj.UnitsOnOrder = src.UnitsOnOrder;
if(src.Category != null && GetChilds)
obj.Category = CategoryDTO.GetDTOFromDALObject(src.Category,false);
if(src.Order_Details != null && GetChilds) {
List<Order_DetailDTO> Order_DetailDTOlst = new List<Order_DetailDTO>();
src.Order_Details.ToList().ForEach(p=>Order_DetailDTOlst.Add(Order_DetailDTO.GetDTOFromDALObject(p,false)));
obj.Order_Details = Order_DetailDTOlst;
}
if(src.Supplier != null && GetChilds)
obj.Supplier = SupplierDTO.GetDTOFromDALObject(src.Supplier,false);
return obj;
}
public ConsoleApplication3.Product GetDALObject(bool IncludeChilds)
{
ConsoleApplication3.Product obj = new ConsoleApplication3.Product();
obj.Discontinued = Discontinued;
obj.ProductID = ProductID;
obj.ProductName = ProductName;
obj.QuantityPerUnit = QuantityPerUnit;
obj.ReorderLevel = ReorderLevel;
obj.UnitPrice = UnitPrice;
obj.UnitsInStock = UnitsInStock;
obj.UnitsOnOrder = UnitsOnOrder;
if(Category != null && IncludeChilds)
obj.Category = Category.GetDALObject(false);
if(Order_Details != null && IncludeChilds) {
System.Data.Objects.DataClasses.EntityCollection<ConsoleApplication3.Order_Detail> Order_Detailsgetdallst = new System.Data.Objects.DataClasses.EntityCollection<ConsoleApplication3.Order_Detail>();
Order_Details.ForEach(p=>Order_Detailsgetdallst.Add(p.GetDALObject(false)));
obj.Order_Details = Order_Detailsgetdallst; }
if(Supplier != null && IncludeChilds)
obj.Supplier = Supplier.GetDALObject(false);
return obj;
}
public Boolean Discontinued { get; set; }
public Int32 ProductID { get; set; }
public String ProductName { get; set; }
public String QuantityPerUnit { get; set; }
public Int16? ReorderLevel { get; set; }
public Decimal? UnitPrice { get; set; }
public Int16? UnitsInStock { get; set; }
public Int16? UnitsOnOrder { get; set; }
public DTO.CategoryDTO Category { get; set; }
public List<DTO.Order_DetailDTO> Order_Details { get; set; }
public DTO.SupplierDTO Supplier { get; set; }
}
}
בנוסף, קיימת האפשרות לא לטעון את האובייקטים שקיים קשר אליהם מה DTO, באמצעות העברת false למתודות העזר GetDTOFromDALObject ו- GetDALObject שקיימות בכל DTO.
כדי לאפשר מעקב קל יותר אחרי גרסאות, יצרתי פרוייקט ב codeplex שנקרא DTOGenerator.
ניתן להוריד את הגרסא הראשונית החדשה (שתומכת גם ב LINQ to SQL וגם ב ADO.NET Entity Framework) מכאן.
בהצלחה.
הקדמה
באחד הפוסטים הקודמים כתבתי על Data Transfer Objects והשימוש בהם לעבודה נכונה ונוחה עם LINQ to SQL ו- ADO.NET Entity Framework.
בין התגובות שקיבלתי בפורום “תכנות .NET” בתפוז שבתגובה לשאלה שעלתה בו פורסם הפוסט, עלה העניין שלמרות שמדובר בכתיבה יותר נוחה, היא דורשת למעשה עבודה כפולה בהתחלה, וגם ציינתי זאת בפוסט. כשמתחילים לכתוב, יש צורך לכתוב DTO נפרד לכל אחת מהישויות עם תחילת העבודה, וגם בד”כ לכתוב מתודות שמבצעות העברה בין הDTO לאובייקט שנמצא ב DAL (האובייקט שמופה ע”י הORM) ולהיפך – לכתוב מתודה שמחזירה את ה DTO בהתאם לאובייקט שנמצא ב DAL. לטעמי, לא מדובר בהרבה עבודה, אבל יש כאלה שזה מפריע להם.
למי שהקצת יותר כתיבת קוד זה מה שמפריע לו, כתבתי היום במסגרת העבודה בסטארטאפסידס Code Generator קטן, שיוצר על סמך DataClasses של LINQ to SQL אובייקטים של DTO מקבילים ותואמים.
LINQ2SQLDTOCreator – האפליקציה עצמה
האפליקציה שכתבתי, ששמה הלא-מקורי-בעליל מופיע בכותרת של הפיסקה (LINQ2SQLDTOCreator), מקבלת נתיב לאסמבלי כלשהו (קובץ DLL של האפליקציה, למשל) ונתיב של המקום אליו היא תפלוט את התוצר שלה. האפליקציה עוברת על כל המחלקות ב DLL ומתעלמת מכולן למעט ממחלקות שמסומנות באטריביוט: System.Data.Linq.Mapping.TableAttribute שמסמל אובייקט שמייצג למעשה מיפוי של טבלה מהדטאבייס.
בכל אחת מהמחלקות הללו עוברת האפליקציה על כל ה properties שמסומנים באטריביוט System.Data.Linq.Mapping.ColumnAttribute שמסמן מיפוי של ה property לעמודה כלשהי בדטאבייס.
בסופו של דבר, מייצרת האפליקציה עבור כל אובייקט בDAL שממופה לטבלה, מחלקה (.cs) שמכילה הגדרות של properties עבור כל אחד מה types (כאשר ה properties הם מאותו ה type שהוגדרו ב DAL – במידה שהם מוגדרים ב database כ Allow Null, אז הם יוגדרו גם ב DTO כ Nullable Types). בנוסף, במחלקה הזאת, כלולות שתי מתודות באופן אוטומטי: מתודה סטאטית שמקבלת מופע מה type של האובייקט שבDAL שממופה לטבלה המקורית ומחזירה DTO, ומתודה שמביאה עבור המופע של הDTO את האובייקט DAL התואם, עם הערכים המתאימים (זה אמנם לא חלק מההגדרה של DTO, אבל שילבתי את זה לשם הנוחות. כמובן, שזה דורש שאם ה DTO בפרוייקט נפרד, אז יהיה רפרנס לDAL, כדי שאפשר יהיה ליצור אובייקט של הDAL ולקבל אותו).
לדוגמא, הנה קובץ שנוצר על-ידי האפליקציה:
1: using System;
2: /*
3: ------------------------DTO OBjECT-----------------------------
4: --------------Generated By LINQ2SQLDTOCreator------------------
5: ----------------Developed by Shahar Gvirtz---------------------
6: ---------http://blogs.microsoft.co.il/blogs/shahar-------------
7: ---------------http://weblogs.asp.net/shahar-------------------
8: */
9:
10: namespace DTO
11: {
12: public class VideoCategoryDTO
13: {
14: public static VideoCategoryDTO GetDTOFromDALObject( DAL.VideoCategory src )
15: {
16: VideoCategoryDTO obj = new VideoCategoryDTO ();
17: obj.ID = src.ID;
18: obj.Name = src.Name;
19: obj.Sorting = src.Sorting;
20:
21: return obj;
22: }
23: public DAL.VideoCategory GetDALObject()
24: {
25: DAL.VideoCategory obj = new DAL.VideoCategory ();
26: obj.ID = ID;
27: obj.Name = Name;
28: obj.Sorting = Sorting;
29:
30:
31: return obj;
32: }
33:
34: public Int32 ID { get; set; }
35: public String Name { get; set; }
36: public Int32 Sorting { get; set; }
37:
38:
39: }
40: }
השימוש באפליקציה הזאת פשוט יחסית:
- הורידו את האפליקציה
- הקובץ שהורדתם כולל את הקוד המלא. בתוך תיקיית הפרוייקט, בנתיב \bin\release תמצאו את קבצי ההרצה הנדרשים. העתיקו את כולם לאיזושהי תיקייה עם נתיב קצר לשם הנוחות.
- האפליקציה היא Console Application. בהרצה, היא מקבלת שני פרמטרים מופרדים ברווח. הראשון, הוא הנתיב ל DLL (אם הוא מכיל רווחים, יש לעטוף אותו במרכאות). השני, הוא הנתיב שבו רוצים את הפלט של הקבצי קוד שנוצרו. למשל:
1: c:\myApp\LINQ2SQLDTOCreator.exe "c:\Dev\App\bin\debug\logic.dll" "c:\outputfromapp"
שימו לב שהפרמטרים מופרדים עם רווח יחיד.
- האפליקציה תרוץ, ובתיקייה שהכנסתם תמצאו קבצים כמספר האובייקטים שממופים לטבלאות אצלכם באסמבלי. כל קובץ מורכב מהשם המקורי בסיומת DTO. את הקבצים האלה עליכם לשלב בפרוייקט (copy paste) במקום שאתם רוצים.
זכרו שאם אתם שמים בפרוייקט נפרד, יש צורך שהוא יכיל רפרנס לפרוייקט שמכיל את ה LINQ to SQL Data Classes.
- בהמשך, כל פעם שאתם מבצעים שינוי, אתם יכולים להריץ את האפליקציה שוב כדי לקבל DTO’s מעודכנים.
מגבלות שימוש ידועות
- האפליקציה אינה תומכת בקשרים בין האובייקטים. אם קיים Relationship בדטאבייס, בתהליך יצירת ה DTO יווצרו properties מתאימים לID בטבלה האחרת, אבל לא יהיה קיים קשר בין האובייקטים השונים.
- האפליקציה מסוגלת לעבוד עם assemblies שה Target Framework שלהם הוא .NET 3.5. היא לא מסוגלת לעבוד עם אסמבליס שה target framework שלהם הוא 4. ההסבר לזה מופיע פה. מי שממש חשוב לו, פשוט צריך לפתוח את הפרוייקט, להגדיר לו target framework של 4 והכל יעבוד טוב. לטובת כאלה שהם לא פריקים של בטות של מיקרוסופט, אני בניתי אותו ב-3.5.
- האפליקציה לא משלבת אוטומטית את הקבצים בפרוייקט ולא דואגת לנושאי רפרנסים וכו’.
במידה שאתם מוצאים בעיות נוספות, אני אשמח לשמוע ולתקן.
החלק הטכני – מה קורה מאחורי הקלעים?
כפי שכבר כתבתי בחלק הקודם, בגדול, מדובר ב Reflection לא מסובך במיוחד. האפליקציה מוצאת בהתאם ל attributes את הנתונים הרלוונטיים מבחינתה (השימוש ב attribute הוא כדי להבחין בין סתם class ל class של ה LINQ to SQL Data Classes. כעיקרון, ה properties היחידים שקיימים ב classes שנוצרים על ידי ה LINQ to SQL הם properties שממופים לעמודות. אבל, בכל זאת אני בודק בהתאם ל attributes, למקרה שהשמשתמש בחר לעשות שינויים בקבצים הללו בעצמו.
באפליקציה קיים Resource שמכיל קובץ טקסט שמהווה למעשה את התבנית למחלקות שנוצרות באמצעות המחולל. בתבנית יש מספר markers (מתחילים בסולמית) שלמקומות האלה מוכנסים (באמצעות replace פשוט) הערכים שנווצרים ע”י האפליקציה על סמך ה reflection.
מבחינת השימוש ב reflection – רובו פשוט למדיץ. הנקודה היחידה שייתכן שפחות מוכרת הוא העבודה מול ה Nullable types. כעיקרון, מי שלא יודע, nullable types זה למעשה מופעים של Nullable<T>. על-מנת לבדוק האם ה type של פרופרטי מסויים הוא nullable או לא, אני בודק האם מדובר ב type גנרי, והאם הוא שווה ל Nullable:
1: if (prop.PropertyType.IsGenericType && prop.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>))
2:
הסיבה לבדיקה, דרך אגב, היא כי אם אני ישר מדפיס את ה type באמצעות ה property שנקרא name, במידה ומדובר ב type גנרי הוא יודפס בצורה חסרת משמעות – למשל, Nullable’ (עם גרש בסוף). על-מנת שהקוד כן יעבור קומפילציה, אני משנה בידת הצורך את הסינטקס לסינטקס סי-שארפי.
סיכום
האפליקציה שהוצגה היא אפליקציה פשוטה, המשתמשת ב Reflection, על-מנת לייצר מחלקות Data Transfer Objects על סמך LINQ to SQL Data Classes. האפליקציה יכולה להיות שימושית מאד למפתחים המשתמשים ב LINQ to SQL ובORM-ים אחרים.
ניתן להוריד את האפליקציה מכאן.
שחר.
אפשרות חמודה שקיימת ב SQL Manegment Studio 2008 היא להגדיר לאופציית ה” GEnerate Scripts” של הדטאבייס גם לייצא את הנתונים (כלומר, לייצר משפטי INSERT לטבלאות).
האפשרות הזאת לא קיימת בגרסא 2005 (יש Generate Scripts – אין Data – כלומר שלמדתי על בשרי כשלא הבנתי לאן נעלמה האפשרות), אבל אין שום מגבלה על התקנה ושימוש ב Management Studio 2008 גם מול instance של SQL Server 2005.
השימוש העיקרי של האפשרות הזאת, הוא העברת מידע משרתי הפיתוח לפרודקשן כאשר רוצים להעביר טבלאות חדשות מסויימות או אובייקטים חדשים מסויימים בקלות. אפשר גם להעביר גטאבייס שלם בהתחלה, במידה שאין אפשרות לבקש ממנהל השרת (נניח, שרת שיתופי) לעשות restore מאיזשהו קובץ bak שאתם שולחים אליו. שימוש אפשרי נוסף הוא לגבות דטאבייס.
אז, איך עושים את זה?
- קליק ימני על הדטאבייס שרוצים לגבות, בחירה ב Tasks->Generate Scripts
- בשלב הבא בוחרים את הדטאבייס שממנו רוצים לגבות. אפשר לסמן כבר בשלב זה את ה checkbox שאומר Script all objects in the selected database. במידה שלא מסמנים אותו, יהיה צריך בהמשך לבחור מה מייצאים מה DB.
- מסך הבא הוא מסך האפשרויות. הרלוונטיות בינהן לרוב המשתמשים הן: Script Database Create (האם לייצר גם שאילתת Create ל-DB. טוב להפעלה הראשונה, אבל אם אתם מייצאים פריטים בודדים – לא תרצו את זה), Script for Server Version – מה גרסאת היעד שלה אמורים הסקריפטים להתאים, Script USE DATABASE – האם הסקריפט יכלול משפט Use ל DB מסויים (עלול להפריע אם שם ה DB בשרת היעד לא תואם לשם ה DB שממנו אתם מייצאים), Script DATA – האפשרות העיקרית שבגללה אני כותב את הפוסט הזה - האם לכלול משפטי INSERT לנתונים.
- בשלב הבא, אם סימנתם לייצר סקריפטים עבור כל האובייקטים ב DB, תעברו ישר לבחירה לאן לייצא את הסקריפט. אחרת, תידרשו קודם לבחור מה האובייקטים ב DB עבורם יווצרו סקריפטים.
- השלב האחרון הוא סיכום, שבו אפשר לראות את האופציות שנבחרו.
בהצלחה.
בעקבות שאלה שקיבלתי, כמה מילים על FooterTemplate ב Repeater: ה FooterTemplate מאפשר לקבוע תבנית עיצובית שתופיע בתחתית ה Repeater לאחר כל ה DataBound Items.
ניתן, כמובן, כמו כל מקום, לשלב שם כל Control שאתם רוצים. וכעת, לשאלה – איך ניגשים אליהם בלי להשתמש באף אחד מה events של ה Repeater? קצת מגושם, לא מורכב. בדוגמא הזאת, נניח של Repeater קוראים rpSample, ל control שאחנו מחפשים קוראים calDate והוא מהסוג Calendar. ככה הקוד ייראה:
Calendar myCal = (Calendar)rpSample.Controls[rpSample.Controls.Count - 1].Controls[0].FindControl("calDate")
בהצלחה.
כשעובדים מול LINQ to SQL או ADO.NET Entities Framework, אנחנו עובדים הרבה מאד מול אובייקט ה DataContext / Entities.
דוגמא מאד נפוצה בדוגמאות ובמדריכים של הטכנולוגיות הללו היא ליצור מופע של המודל בכל מתודה וכל פעם שמשתמשים בו. למשל:
1: public static void Method1()
2: {
3: using (var data = new DataClassesDataContext())
4: {
5: //Code
6: }
7: }
8: public static void Method2()
9: {
10: using (var data = new DataClassesDataContext())
11: {
12: //Code
13: }
14: }
וכך יש הרבה מתודות, כל אחת עושה new DataClassesDataContext. ואז מגיעה היום שבו רוצים לעשות איזשהו שינוי מינורי. רוצים לעשות connection string שונה בהתאם לסביבה או בהתאם לאיזשהו תנאי. איך עושים את זה? רמז – במקרה הזה, זה כנראה יהיה (במקרה הטוב) find&replace והרבה עבודת ידיים (במקרה הרע). וזה עוד לפני שדיברנו על דברים יותר מורכבים.
הפיתרון, כדי לחסוך עבודה במצבים כאלה ולהיות יותר גמישים לשינוי, הוא לכתוב איזושהי FactoryMethod. מתודה שמחזירה בעצמה מופע של DataClassesDataContext עם כל ההגדרות הנחוצות. אפשר כמובן לכתוב אותה בצורה גנרית, שתאפשר לנו גם לשנות בעתיד את סוג האובייקט שמוחזר, או לעבור בקלות ל ORM אחר, כל עוד הוא ממש איזשהו אינטרפייס – אבל זה במרבית המקרים יהיה קצת overkill. מה שכן יכול להתגלות כמאד משתלם, זה למקם במקום אחר מתודה כזאת:
1: public static DataClassesDataContext GetDataContextInstance()
2: {
3: return new DataClassesDataContext();
4: }
ואז לשנות את המתודות בהתאם, שבמקום לעשות new DataClassesDataContext הן יעבדו עם המתודה הזאת. בצורה הזאת, נניח אם מחר צריך לעשות Connection String שונה בהתאם לצורך, או אפילו שינויים יותר משמעותיים – הכל מרוכז במקום אחד.
בהצלחה.
אם רוצים ליצור צלמית של תמונה (גרסא מוקטנת שלה), ניתן כמובן לחתוך אותה באופן ידני. אולם, למי שלא מכיר (והרבה, כנראה, לא מכירים) הפונקציונאליות הזאת כבר מובנת ב BCL במחלקה System.Drawing.Image.
השימוש במתודה הזאת, GetThumbnailImage מאד פשוט. הנה לדוגמא קוד של Console Application שיוצר גרסא מוקטנת לכל התמונות בפורמט jpg שבתיקייה:
נעבור שנייה על הקוד הזה (שכפי שאתם רואים, הוא להדגמה בלבד):
1: ...
2: using System.Drawing;
3: using System.IO;
4: ...
5:
6: string dir = @"C:\Users\Public\Pictures\Sample Pictures";
7: int height = 60;
8: int width = 60;
9: string[] Images = Directory.GetFiles(dir, "*.jpg");
10: foreach (var image in Images)
11: {
12: Image.FromFile(image)
13: .GetThumbnailImage(width, height, delegate() { return false; }, IntPtr.Zero)
14: .Save(string.Format(@"c:\tmp\{0}",Path.GetFileNameWithoutExtension(image) + "_thumb.jpg"));
15: }
בשורות 6-8 אני מגדיר hard-coded את המידע שאני רוצה (dir היא תיקיית התמונות המקורית, ו height ו width מעידים על עצמם).
בשורה 9, אני מקבל את כל הקבצים שהסיומת שלהם jpg ונמצאים בתיקייה שציינתי.
לאחר מכן, אני עובד על כל הקבצים כאשר, בשורות 12-14 אני מבצע את הדברים הבאים: עבור כל נתיב, אני מקבל System.Drawing.Image. ובאמצעות המתודה GetThumbnailImage (שמקבלת רוחב, גובה, delegate ואת הערך של IntPtr.Zero שחייב להיות ככה) אני מקבל אובייקט Image נוסף, המכיל את התמונה המוקטנת, שאותו אני שומר לדיסק.
החיסרון בשיטה הזאת, הוא שאני לא שומר על יחס התמונה. במידה והיה מדובר במקרה אמיתי, צריך לוודא שהרוחב והגובה שנקבעים תואמים את יחס התמונה המקורי, אחרת נקבל תמונה לא פרופורציונאלית.
בהצלחה.
----------------------------
תודה, תודה, תודה לשלומי בן שמואל שהמליץ לי על התוסף הזה לכתיבת קוד. סוף סוף משהו שבאמת עובד!
כאשר מוסיפים שורה לטבלה המכילה איזושהי עמודה בשם ID שמוגדרת, לצורך העניין, כ Primary Key, פעמים רבות נרצה לקבל מיד אחרי הוספת הרשומה את ה ID שהוקצה לה.
באמצעות T-SQL, זה היה נעשה עם SCOPE_IDENTIFY(), בצורה הזאת:
1: SELECT SCOPE_IDENTIFY() AS [NewID]
זה היה מגיע לאחר ה INSERT, היינו מקבלים את הID וכולם היו שמחים ומאושרים. היום ראיתי דוגמאת קוד שבה מיד לאחר הוספת השורה ב LINQ to SQL וביצוע SubmitChanges, הולכים לבטלה ושולפים את השורה האחרונה בה כדי לקבל את הID. בסופו של דבר, התהליך הזה מיותר.
ברגע שמשתמשים במתודה InsertOnSubmit ומעבירים לה אובייקט שלו מוגדר, למשל, Primary Key מסוג int שמוגדר כ IsIdentify = True, לאחר ביצוע SubmitChanges ערך הID החדש יוכנס לאובייקט. למשל:
1: using (var data = new DataClassesDataContext())
2: {
3: Event ev = new Event(); //כאשר מדובר באובייקט שממופה לטבלה בדטאבייס
4: ev.Prop1 = "value1";
5: ev.Prop2 = "value2"
6: ....
7: data.Events.InsertOnSubmit(ev);
8: data.SubmitChanges();
9: Console.WriteLine(ev.ID); //ID is PK
10: }
אז, כאשר ID לא הוגדר ידנית, אבל הוא מוגדר באמת כ Primary Key, אז בעת ההוספה ל DB, מאחורי הקלעים באמת יישלף ה SCOPE_IDENIFY ויושם אוטומטית ל property של הPrimary Key באובייקט שהוספנו. נוח ושימושי.
בהצלחה.
נ.ב. חייב בדחיפות המלצה על דרך נורמלית לכתוב קוד באמצעות Windows Live Writer עם תוסף שגם יידע ליישר את הקוד לשמאל ורצוי שגם יידע לצבוע אותו. והכי חשוב, שאני לא אצטרך להעתיק ידנית את הקוד… תודה מראש לעוזרים.
בפורום “תכנות .NET” בתפוז, עלה לאחרונה נושא השילוב בין ADO.NET Entity Framework ומודל השכבות. ובאמת, אם נסתכל על הדוגמאות הנפוצות באינטרנט, מאד קל לראות עמודים ששמכילים ב code behind שלהם שאילתות LINQ שמקומם ב DAL ופעולות לוגיות שונות שמקומם ב BLL. בסופו של דבר, יש משהו מאד מפתה בנוחות שעלול לגרום ליצירת קוד מבולגן.
אם תחשבו על אפליקציה הבנוייה עפ”י מודל השכבות, מאד ברור לנו מה אמור להיות ב DAL ומה אמור להיות ב BLL ומה אמור להיות בGUI, למשל. אבל, השאלה היא, כשהאפליקציה מתבססת על LINQ to SQL/ ADO.NET EF כשכבת ה DAL, היא איך אנחנו מעבירים את המידע בין השכבות. שיטה שמאד מאד מקובלת בכל מיני דוגמאות באינטרנט, היא להשתמש באובייקטים שמופו מה DB ע”י ה ORM כBO’s ולמעשה, אותם להעביר בין השכבות.
שיטה זו בעייתית. דבר ראשון, האובייקטים שנוצרים באמצעות המיפוי של הORM-ים האלה עמוסים בהרבה מאד… זבל (לפחות, עד הגרסא הבאה של EF שתתמול בעבודה עם POCO). כלומר, דברים מאד מאד חשובים, אבל לא דברים שחשובים לאובייקטים ב BO שלנו. הם גם יורשים וממשים כל מיני דברים שיכולים “לבלגן” את האפליקציה שלנו (כל מתודות ה OnInsert וכו’).
הבעייתיות השנייה, היא ששימוש באובייקטים האלה כ-ORM כופה עלינו תלות משמעותית מדי ב ORM. אנחנו מסתמכים על האובייקטים שהוא מייצר בצורה שתגרום לכך שאם מתישהו נצטרך להחליף, התיקונים שלנו יהיו הרבה מעבר לתיקונים בDAL, אלא נצטרך לתקן גם ב BLL וגם ב GUI. או לחלופין, במידה שנחליף, נצטרך לעשות שמיניות באוויר כדי לשמור על איזושהי תאימות.
וחוץ מזה, נוצרת איזושהי פגיעה בהפרדה בין השכבות כשלGUI יש רפרנס ישיר ל DAL. המצב האידיאלי מבחינתי, הוא שלGUI יש רפרנס ל BLL שמחזיק בתורו רפרנס ל DAL ושלכולם יש רפרנס לBO’s.
אז, מה עושים? מה צורת העבודה המומלצת?
התשובה לזה קצת מורכבת, כי יש הרבה שיטות. השיטה שלי היא להשתמש ב DTO’s שגם משמשים כסוג של BO.
Data Transfer Objects הם אובייקטים שהייעוד העיקרי שלהם הוא העברת מידע בין השכבות. הם כמעט תמיד לא יכילו שום פונקציונאליות, למעט הצהרה על properties עם getters ו setters.
דוגמא מעשית:
כאשר משכבת ה GUI אני רוצה להוסיף, נניח, מאמר. לחיצה על כפתור ההוספה תגרור יצירת מופע של BO.ArticleDTO (אובייקט, שתפקידו להחזיק את המידע). המופע הזה יועבר כפרמטר למתודה שנמצאת, נניח, ב BLL.Articles.Insert. בBLL, למשל, אני אשלח אימייל על המאמר החדש שהתווסף לרשימת התפוצה, ואקרא ל DAL.Articles.Insert שאעביר אליו גם כן את אותו המופע של BO.ArticleDTO. בשכבת ה DAL, אני אצור מופע של DAL.Article (שהוא האובייקט שיצר ה ORM ושאיתו הוא עובד, כי הוא הרי לא מכיר את הDTO שלי) ואבצע את ההוספה.
במקרה של שליפה, מה ששכבת ה DAL תחזיר הלאה, זה גם כן מופע של BO.ArticleDTO.
חוץ מהעובדה ששימוש בשיטה הזאת פותר, למעשה, את הבעיות שציינתי כסיבה לא להשתמש באובייקטים שיוצר ה ORM, הוא גם נותן כמה יתרונות נוספים: דבר ראשון, ה DTO לא צריך לחפוך בשום צורה שהיא למודל ב DB, אין שום חובה שכל הנתונים שבו יהיו מקושרים ל DB, ולמעשה יש חופש מוחלט לגבי המבנה שלו.
מעבר לכך, במידה שקורה איזשהו שינוי בשכבת ה DAL, אין שום צורך לגעת באף שכבה עליונה יותר – כולם מכירים את ה DTO ועובדים מולו, והוא מאפשר ליצור איזושהי חציצה בין השכבות.
כמובן, שלשיטה הזאת יש חסרון – צריך, בפעם הראשונה, לעשות עבודה כפולה. ליצור את ה DTO. אבל, כבר לא פעם השיטה הזאת חסכה לי לא מעט עבודה בהמשך ואני ממליץ עליה בחום כשיטת עבודה כשעובדים עם ADO.NET EF או עם LINQ to SQL, לפחות עד ש EF יציג תמיכה סבירה בעבודה עם POCO.
בהצלחה.
במסגרת העבודה שלי בסטארטאפסידס, פיתחתי לאחרונה צ’אט אחד על אחד (סגנון gmail, פייסבוק וכו’) המאפשר לרשומים באתר לשוחח אחד עם השני.
מי שמכיר את הדוגמאות שנמצאות באינטרנט, בד”כ הנושא ממומש באמצעות שימוש כלשהו ב database, דבר שבאפליקציה שלנו היה בלתי אפשרי, ולכן החלטתי להשתמש במנגנון ה caching של ASP.NET. כתבתי באתר סטארטאפסידס מאמר קצר על מאחורי הקלעים של סידס צ’אט.
אתם מוזמנים לקרוא.
שחר.
אחת הפעולות הכי שימושיות שקיימות על מערכים, היא indexOf (אתה נותן את הערך של הפריט, וקמבל את האינדקס שלו במערך). כרום, פיירפוקס, ספארי - כל אלה תומכים בפעולה הזאת כמשהו מובנה,במנוע javascript שלהם על אף שהיא לא ב specification הרשמי. אינטרנט אקספלורר, בכל אופן, לא תומך בה.
אז מה עושים? יש פיתרון שמסתובב ברשת שלקוח למעשה מהמימוש של מוזילה עבור indexOf, שאפשר להוסיף אותו כ prototype function באקספלורר. הבעייה – מסתבר שזה גורם לעיתים בצורה לא מוסברת לאקספלורר להיתקע.
הפיתרון היותר נוח והיותר פשוט (בהנחה שאתם משתמשים ב jQuery) הוא לעבוד עם jQuery.inArray(a,b). כאשר הפרמטר הראשון (a) הוא ה value שאתם מחפשים והפרמטר השני (b) הוא המערך.
יחי jQuery.
כאשר עובדים עם profiler ניתן לייצא את הנתונים (וכך בד”כ עושים) לקובץ trc. במידה ורוצים לעבוד מול קבצי trc בקלות (בלי צורך בכל ה performance tools), ניתן לעבוד מולם באמצעות שאילתות TSQLפשוטות, בדיוק כמו טבלה. לצורך העניין, ניתן להשתמש ב fn_trace_gettable שמקבלת את שם הקובץ ומספר המסמן את כמות הקבצים שצריך לקרוא.
לצורך הדוגמא, השאילתה הבאה תחזיר לנו את השאילתה עצמה, שם האפליקציה שהריצה את השאילתה, זמן הפעולה שלה והCPU שצרכה ותמיין לפי הזמן שלקח להריץ כל שאילתה:
SELECT textdata,applicationname,duration,CPU
FROM ::fn_trace_gettable(('c:\startup.trc'),default)
WHERE duration is not null and textdata is not null
ORDER BY duration DESC
.csharpcode, .csharpcode pre
{
font-size: small;
text-direction: ltr;
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; }
בהצלחה.
More Posts
Next page »