در بیشتر مواقع نیازی به ارائه پروایدرهای پایگاه داده چندگانه ندارید. به عنوان مثال ، شما با پروایدرSQL Server شروع میکنید و نیازی به رفتن به پروایدر پایگاه داده دیگری نیست. در این پروژهی نمونه، ما باید چندین پروایدر را برای پشتیبانی از محیطهای مختلف استقرار اضافه کنیم. برخی از مشتریان ترجیح میدهند از پایگاه داده Windows Server و SQL Server و برخی دیگر از Linux و MySQL یا PostgreSQL استفاده کنند.
متد زیر هستهی اصلی عملیات است و کلیهی نگاشتهای لازم را انجام میدهد. این متد وظیفهی تبدیل نگاشتها را دارد. نگاشتهایی که با Attributes مشخص شدهاند:
public static void Initialize(Assembly assembly) { //register global convertors. AutoMapper.Mapper.CreateMap<DateTime, string>().ConvertUsing<DateTimeToPersianDateTimeConverter>(); var typesToMap = from t in assembly.GetTypes() let attr = t.GetCustomAttribute<MapFromAttribute>() where attr != null select new {SourceType = attr.SourceType, Destination = t, Attribute = attr}; foreach (var map in typesToMap) { AutoMapper.Mapper.CreateMap(map.SourceType, map.Destination) .DoMapForMemberAttribute() // for different property names in source and destination .DoIgnoreMapAttribute()// ignore specified properties .DoUseValueResolverAttribute()// set value resolvers .DoIgnoreAllNonExisting()// its have to be the latest. ; } //endeach AutoMapper.Mapper.AssertConfigurationIsValid(); }
در سطر اول، اقدام به رجیستر کردن کلیهی مبدلهای سراسری میکنیم. در این سطر مبدل تاریخ به کوچی خورشیدی مورد استفاده قرار گرفته است. سپس در اسمبلی داده شده، کلیه نوعهایی که ویژگی MapFromAttribute را دارند، یافته و جدا میکنیم. در حلقهی foreach ابتدا نگاشت نوع مبدأ و مقصد را انجام میدهیم. خروجی این متد از نوع IMappingExpression است. گر چه این اینترفیس برای تغییر بسته است، ولی قابل توسعه میباشد و عملیات را توسط متدهای الحاقی انجام میدهیم(اصل OCP).
اگر به نحوهی نامگذاری متدهای الحاقی تعریف شده دقت کرده باشید، تنها کلمهی Do به ابتدای نام ویژگیها اضافه شده است.
متد الحاقی DoMapFormMemberAttribute
public static IMappingExpression DoMapForMemberAttribute(this IMappingExpression expression) { var ok = from p in expression.TypeMap.DestinationType.GetProperties() let attr = p.GetCustomAttribute<MapForMemberAttribute>() where attr != null select new {AttributeValue = attr, PropertyName = p.Name}; foreach (var property in ok) { expression.ForMember(property.PropertyName, opt => opt.MapFrom(property.AttributeValue.MemberToMap)); } return expression; }
متد الحاقی DoIgnoreMapAttribute
public static IMappingExpression DoIgnoreAttribute(this IMappingExpression expression) { foreach (var property in expression.TypeMap.DestinationType.GetProperties() .Where(x => x.GetCustomAttribute<IgnoreMapAttribute>() != null)) { expression.ForMember(property.Name, opt => opt.Ignore()); } return expression; }
متد الحاقی DoUseValueResolverAttribute
public static IMappingExpression DoUseValueResolverAttribute(this IMappingExpression expression) { var ok = from p in expression.TypeMap.DestinationType.GetProperties() let attr = p.GetCustomAttribute<UseValueResolverAttribute>() where attr != null select new {AttributeValue = attr, PropertyName = p.Name}; foreach (var property in ok) { expression.ForMember(property.PropertyName, opt => opt.ResolveUsing(property.AttributeValue.ValueResolver)); } return expression; }
متد الحاقی DoIgnoreAllNonExisting
public static IMappingExpression DoIgnoreAllNonExisting(this IMappingExpression expression) { var attr = expression.TypeMap.DestinationType.GetCustomAttribute<MapFromAttribute>(); if (attr?.IgnoreAllNonExistingProperty == false)//instead of if(attr == null || attr.IgnoreAllNonExistingProperty == false) return expression; foreach (var property in expression.TypeMap.GetUnmappedPropertyNames()) { expression.ForMember(property, opt => opt.Ignore()); } return expression; }
توضیح تکمیلی: پس از تنظیم کلیهی نگاشتها در automapper جهت اطمینان از صحت تنظیمات، فراخوانی متد AutoMapper.Mapper.AssertConfigurationIsValid الزامی است. یکی از عواملی که باعث شکست این متد میشود، وجود پروپرتیهایی در نوع مقصد است، بطوریکه معادل اسمی در نوع مبدأ نداشته باشند و یا تنظیمی جهت مشخص سازی نگاشت آن انجام نشده باشد (پروپرتی که قابل نگاشت نباشد). در حقیقت این شکست بسیار مفید است. به این صورت که اگر این شکست صورت نگیرد در حین نگاشت مقادیر، باید از null یا مقدار default بدون اطلاع برنامه نویس برای مقداردهی پروپرتی استفاده کند و این یک حالت نامعلوم شیء است. اگر میخواهید این پروپرتیها مقدار پیشفرضی بگیرند و همچنین باعث شکست عملیات هم نشوند، باید بطور صریح این موضوع را اعلام کنید. این اعلام یا باید به همین روش صورت بگیرد یا باید از ویژگی IgnorMapAttribute استفاده شود. تنها تفاوت این دو، نحوهی اعمال تنظیم میباشد. IgnorMapAttribute باید روی تک تک پروپرتیهای مدنظر قرار گیرد، ولی در روش اول تنها کافیست که مقدار true تنظیم گردد. بهنظر استفاده از IgnoreMapAttribute باعث طولانی شدن کدها میشود؛ اما توصیه میشود که از همین شیوه استفاده کنید.
تا اینجا کدهای مورد نیاز نوشته شدند. در ادامه به ارائهی یک مثال برای نگاشت اشیاء در Automapper توسط Attributeها میپردازم.
مدل سادهی زیر را در نظر بگیرید:
public class Student { public virtual int Id { set; get; } public virtual string Name { set; get; } public virtual string Family { set; get; } public virtual string Email { set; get; } public virtual DateTime RegisterDateTime { set; get; } public virtual ICollection<Book> Books { set; get; } } public class Book { public virtual int Id { set; get; } public virtual string Name { set; get; } public virtual DateTime BorrowDateTime { set; get; } public virtual DateTime ExpiredDateTime { set; get; } public virtual decimal Price { set; get; } [ForeignKey("StudentIdFk")] public virtual Student Student { set; get; } public virtual int StudentIdFk { set; get; } }
[MapFrom(typeof (Student), ignoreAllNonExistingProperty: true, alsoCopyMetadata: true)] public class AdminStudentViewModel { // [IgnoreMap] public int Id { set; get; } [MapForMember("Name")] public string FirstName { set; get; } [MapForMember("Family")] public string LastName { set; get; }
[IgnoreMap] public string Email { set; get; } [MapForMember("RegisterDateTime")] public string RegisterDateTimePersian { set; get; } [UseValueResolver(typeof (BookCountValueResolver))] public int BookCounts { set; get; } [UseValueResolver(typeof (BookPriceValueResolver))] public decimal TotalBookPrice { set; get; } };
public class BookCountValueResolver : ValueResolver<Student, int> { protected override int ResolveCore(Student source) => source.Books.Count; }; public class BookPriceValueResolver : ValueResolver<Student, decimal> { protected override decimal ResolveCore(Student source) => source.Books.Sum(b => b.Price); };
static void Main(string[] args) { var assemblyToLoad = Assembly.GetAssembly(typeof (AdminStudentViewModel));//get assembly global::AttributesForAutomapper.Configuration.Initialize(assemblyToLoad);//init automaper IList<Student> lst; using (var context = new MySampleContext()) { lst = context.Students.Include(x => x.Books).ToList(); } foreach (var student in lst) { WriteLine( $"[{student.Id}]*\n{student.Name} {student.Family}.\nmailto:{student.Email}.\nRegistered at'{student.RegisterDateTime}'"); foreach (var book in student.Books) WriteLine($"\tBook name:{book.Name}, Book price:{book.Price}"); } var lstViewModel = AutoMapper.Mapper.Map<IList<Student>, IList<AdminStudentViewModel>>(lst); foreach (var adminStudentViewModel in lstViewModel) { WriteLine( $"[{adminStudentViewModel.Id}]*\n\t{adminStudentViewModel.FirstName} {adminStudentViewModel.LastName}.\n\t" + $"mailto:{adminStudentViewModel.Email}.\n\tRegistered at'{adminStudentViewModel.RegisterDateTimePersian}'\n\t" + $"Book Counts: {adminStudentViewModel.BookCounts} with total price of {adminStudentViewModel.TotalBookPrice}"); } WriteLine("Press any key to exit..."); ReadKey(); }
نمونهی خروجی:
[1]* Morteza Raeisi. mailto:MrRaeisi@outlook.com. Registered at'23/08/1392 19:11:43' // I'm using Windows 10 with Persian calendar as default, On other OS or calendar settings, this value is different. Book name:AutoMapper Attr, Book price:1000.00 Book name:Second Book, Book price:2500.00 Book name:Hungry Book, Book price:2500.00 ... [1]* Morteza Raeisi. //MapForMemebers mailto:. // IgnoreMap Registered at'1392/08/23 19:11' // Convert using Book Counts: 3 with total price of 6000.00 // Value resolvers ...
The program can't start because api-ms-win-crt-runtime-l1-1-0.dll is missing from your computer. Try reinstalling the program to fix this problem.
the library hostfxr.dll was found, but loading it from C:\<path_to_app>\hostfxr.dll failed
- CUME_DIST:
CUME_DIST( ) OVER ( [ partition_by_clause ] order_by_clause )
Create Table TestCUME_DIST (SalesOrderID int not null, OrderQty smallint not null, ProductID int not null ); GO Insert Into TestCUME_DIST Values (43663,1,760),(43667,3,710),(43667,1,773), (43667,1,775),(43667,1,778),(43669,1,747), (43670,1,709),(43670,2,710),(43670,2,773),(43670,1,776)
SELECT SalesOrderID, OrderQty, CUME_DIST() OVER(ORDER BY SalesOrderID) AS [CUME_DIST] FROM TestCUME_DIST ORDER BY [CUME_DIST] DESC
*** برای بدست آوردن CUME_DIST سطر پنجم نیز خواهیم داشت:
Rows=(c1+c2)/c3 بنابراین خواهیم داشت: 0/6=10/(5+1)=Rows
مثال دوم : ابتدا Script زیر را اجرا نمایید:
SELECT SalesOrderID, OrderQty, ProductID, CUME_DIST() OVER(PARTITION BY SalesOrderID ORDER BY ProductID ) AS [CUME_DIST] FROM TestCUME_DIST WHERE SalesOrderID IN (43670, 43669, 43667, 43663) ORDER BY SalesOrderID DESC, [CUME_DIST] DESC
همانگونه که ملاحظه میکنید، در این مثال، خروجی، براساس SalesOrderID به چهار گروه تقسیم میشود و عملیات مرتب سازی روی فیلد ProductID انجام می گیرد، بنابراین CUME_DIST، روی هر گروه بر روی فیلد ProductID محاسبه میشود.
گروه اول : نحوه محاسبه Cume_DIST سطر اول:
سوال:چه تعداد از مقادیر ProductID آن برابر 776 میباشد؟
جواب: فقط مقدار سطر اول، بنابراین خواهیم داشت C1=1
سوال: چه تعداد از مقادیر کوچکتر از ProductID=776 میباشد؟
جواب: مقدار سه سطر، در واقع مقادیر سطر دوم،سوم و چهارم کوچکتر از مقدار سطر اول میباشند، c2=3
سوال: تعداد کل سطرهای گروه اول چه مقدار میباشد؟
جواب: 4سطر
بنابراین برای بدست آوردن CUME_DIST سطر اول خواهیم داشت:
1=4/(1+3)=Rows
محاسبه سطر دوم از گروه اول بدون شرح:
0/75=4/(1+2)=Rows
امیدوارم مفید واقع شده باشد.
میتوانیم در یک جدول از دیتابیس مان بر اساس یک شرط خاص, عملیات Insert,Delete,Update را مدیریت کنیم.
بعنوان مثال در قطعه کد زیر ما قبل از عملیات Insert در جدول tblTest چک میکنیم که اگر مقدار ستون FirstName برابر با null بود عملیات Insert آن رکورد در دیتابیس لغو شود.
ALTER TRIGGER [dbo].[Prevent_Befor_Insert_Null] ON [dbTest].[dbo].[tblTest] INSTEAD OF INSERT AS BEGIN SET NOCOUNT ON IF OBJECT_ID(N'dbTest.dbo.tblTest.FirstName') is null BEGIN DECLARE @Id int SET @Id = (select Id from inserted) RAISERROR ('مقدار فیلد نام نباید خالی باشد',16,1) ROLLBACK END END
از دو طریق میتوان به مقادیر فیلدهای رکورد جاری دسترس داشت:
1- استفاده از OBJECT_ID و ذکر نام فیلد مورد نظر
2- گرفتن فیلد مورد نظر از جدول INSERTED یا DELETED
DML Triggerها دارای دو جدول خاص بنامهای INSERTED و DELETED هستند که توسط خود SQL Server مدیریت میشوند.در حقیقت در پشت صحنه, ما با این دو جدول در هنگام تغییر مقادیر دادههای جداول دیتابیس کار میکنیم و نمیتوانیم بصورت مستقیم دادههای جداول موجود در دیتا بیس مان را تغییر دهیم.
جدول INSERTED و DELETED حاوی رکورد جاری است که تحت تاثیر عمل درج, ویرایش و حذف در دیتابیس قرار گرفته است.
اطلاعات بیشتر در اینجا و اینجا
دوره 3 ساعته PostgreSQL
PostgreSQL Tutorial Full Course 2022
I provide here in this PostgreSQL tutorial a full course you can use to master PostgreSQL. Postgres is an object relational database that is just as fast as MySQL that adheres more closely to SQL standards and excels at concurrency. Postgres is also superior at avoiding data corruption.
TABLE OF CONTENTS
00:00 Intro
00:30 Why Use Postgres?
01:13 What is a Database
03:12 Change Database Theme
03:53 Create a Database
04:46 Design a Database
05:50 Turn Invoice into a Database
07:04 Make a Table
12:13 Data Types
16:36 Adding Data to Table
18:15 To See Data
18:25 SELECT
19:19 Create Custom Type
20:48 Change Column Data Type
22:58 Thinking About Tables
25:37 Breaking Up Tables
27:03 Primary & Foreign Keys
32:40 Foreign & Primary Keys
33:28 Altering Tables Many Examples
53:00 Getting Data from One Table
53:40 Where
54:30 Conditional Operators
55:48 Logical Operators
58:12 Order By
59:32 Limit
1:01:45 GROUP BY
1:03:11 Distinct
1:05:00 Getting Data from Multiple Tables
1:05:21 Inner Join
1:08:50 Join 3 Tables
1:13:15 Arithmetic Operators
1:13:45 Join with Where
1:14:55 Outer Joins
1:17:03 Cross Joins
1:18:16 Unions
1:19:27 Extract
1:21:05 IS NULL
1:22:03 SIMILAR LIKE & ~
1:29:25 GROUP BY
1:31:14 HAVING
1:32:18 AGGREGATE FUNCTIONS
1:34:22 WORKING WITH VIEWS
1:45:01 SQL Functions
1:49:00 Dollar Quotes
1:50:06 Functions that Return Void
1:52:38 Get Maximum Product Price
1:53:39 Get Total Value of Inventory
1:54:26 Get Number of Customers
1:56:15 Named Parameters
2:01:30 Return a Row / Composite
2:03:38 Get Multiple Rows
2:07:08 PL/pgSQL
2:11:35 Variables in Functions
2:15:55 Store Rows in Variables
2:19:17 IN INOUT and OUT
2:21:01 Using Multiple Outs
2:25:56 Return Query Results
2:33:42 IF ELSEIF and ELSE
2:38:48 CASE Statement
2:42:01 Loop Statement
2:45:20 FOR LOOP
2:48:34 Result Sets, Blocks & Raise Notice
2:51:11 For Each and Arrays
2:53:20 While Loop
2:54:54 Continue
3:01:34 Stored Procedures
3:09:35 Triggers
3:29:25 Cursors
3:39:45 Installation
01. Introduction to Services 02. Demo - Creating Your First Custom Service 03. Demo - Another Custom Service Example 04. Introduction to Built-In AngularJS Services 05. Demo - Using the $http and $q Services Together 06. Demo - Using the $rsource and $q Services 07. Demo - Using the $anchorScroll Service 08. Demo - Using the $cacheFactory Service 09. Demo - Using the $compile Service 10. Demo - Using the $parse Service 11. Demo - Using the $locale Service 12. Demo - Using the $timeout Service 13. Demo - Using the $exceptionHandler Service 14. Demo - Using the $filter Service 15. Demo - Using the $cookieStore Service 16. Overview of Less Common Services 17. Suggested Exercises
'use strict'; eventsApp.factory('gravatarUrlBuilder', function() { return { buildGravatarUrl: function(email) { // try angularjsdemo@gmail.com as an email var defaultGravatarUrl = "http://www.gravatar.com/avatar/000?s=200"; var regex = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; if (!regex.test(email)) return defaultGravatarUrl; var MD5=function(s){function L(k,d){return(k<<d)|(k>>>(32-d))}function K(G,k){var I,d,F,H,x;F=(G&2147483648);H=(k&2147483648);I=(G&1073741824);d=(k&1073741824);x=(G&1073741823)+(k&1073741823);if(I&d){return(x^2147483648^F^H)}if(I|d){if(x&1073741824){return(x^3221225472^F^H)}else{return(x^1073741824^F^H)}}else{return(x^F^H)}}function r(d,F,k){return(d&F)|((~d)&k)}function q(d,F,k){return(d&k)|(F&(~k))}function p(d,F,k){return(d^F^k)}function n(d,F,k){return(F^(d|(~k)))}function u(G,F,aa,Z,k,H,I){G=K(G,K(K(r(F,aa,Z),k),I));return K(L(G,H),F)}function f(G,F,aa,Z,k,H,I){G=K(G,K(K(q(F,aa,Z),k),I));return K(L(G,H),F)}function D(G,F,aa,Z,k,H,I){G=K(G,K(K(p(F,aa,Z),k),I));return K(L(G,H),F)}function t(G,F,aa,Z,k,H,I){G=K(G,K(K(n(F,aa,Z),k),I));return K(L(G,H),F)}function e(G){var Z;var F=G.length;var x=F+8;var k=(x-(x%64))/64;var I=(k+1)*16;var aa=Array(I-1);var d=0;var H=0;while(H<F){Z=(H-(H%4))/4;d=(H%4)*8;aa[Z]=(aa[Z]|(G.charCodeAt(H)<<d));H++}Z=(H-(H%4))/4;d=(H%4)*8;aa[Z]=aa[Z]|(128<<d);aa[I-2]=F<<3;aa[I-1]=F>>>29;return aa}function B(x){var k="",F="",G,d;for(d=0;d<=3;d++){G=(x>>>(d*8))&255;F="0"+G.toString(16);k=k+F.substr(F.length-2,2)}return k}function J(k){k=k.replace(/rn/g,"n");var d="";for(var F=0;F<k.length;F++){var x=k.charCodeAt(F);if(x<128){d+=String.fromCharCode(x)}else{if((x>127)&&(x<2048)){d+=String.fromCharCode((x>>6)|192);d+=String.fromCharCode((x&63)|128)}else{d+=String.fromCharCode((x>>12)|224);d+=String.fromCharCode(((x>>6)&63)|128);d+=String.fromCharCode((x&63)|128)}}}return d}var C=Array();var P,h,E,v,g,Y,X,W,V;var S=7,Q=12,N=17,M=22;var A=5,z=9,y=14,w=20;var o=4,m=11,l=16,j=23;var U=6,T=10,R=15,O=21;s=J(s);C=e(s);Y=1732584193;X=4023233417;W=2562383102;V=271733878;for(P=0;P<C.length;P+=16){h=Y;E=X;v=W;g=V;Y=u(Y,X,W,V,C[P+0],S,3614090360);V=u(V,Y,X,W,C[P+1],Q,3905402710);W=u(W,V,Y,X,C[P+2],N,606105819);X=u(X,W,V,Y,C[P+3],M,3250441966);Y=u(Y,X,W,V,C[P+4],S,4118548399);V=u(V,Y,X,W,C[P+5],Q,1200080426);W=u(W,V,Y,X,C[P+6],N,2821735955);X=u(X,W,V,Y,C[P+7],M,4249261313);Y=u(Y,X,W,V,C[P+8],S,1770035416);V=u(V,Y,X,W,C[P+9],Q,2336552879);W=u(W,V,Y,X,C[P+10],N,4294925233);X=u(X,W,V,Y,C[P+11],M,2304563134);Y=u(Y,X,W,V,C[P+12],S,1804603682);V=u(V,Y,X,W,C[P+13],Q,4254626195);W=u(W,V,Y,X,C[P+14],N,2792965006);X=u(X,W,V,Y,C[P+15],M,1236535329);Y=f(Y,X,W,V,C[P+1],A,4129170786);V=f(V,Y,X,W,C[P+6],z,3225465664);W=f(W,V,Y,X,C[P+11],y,643717713);X=f(X,W,V,Y,C[P+0],w,3921069994);Y=f(Y,X,W,V,C[P+5],A,3593408605);V=f(V,Y,X,W,C[P+10],z,38016083);W=f(W,V,Y,X,C[P+15],y,3634488961);X=f(X,W,V,Y,C[P+4],w,3889429448);Y=f(Y,X,W,V,C[P+9],A,568446438);V=f(V,Y,X,W,C[P+14],z,3275163606);W=f(W,V,Y,X,C[P+3],y,4107603335);X=f(X,W,V,Y,C[P+8],w,1163531501);Y=f(Y,X,W,V,C[P+13],A,2850285829);V=f(V,Y,X,W,C[P+2],z,4243563512);W=f(W,V,Y,X,C[P+7],y,1735328473);X=f(X,W,V,Y,C[P+12],w,2368359562);Y=D(Y,X,W,V,C[P+5],o,4294588738);V=D(V,Y,X,W,C[P+8],m,2272392833);W=D(W,V,Y,X,C[P+11],l,1839030562);X=D(X,W,V,Y,C[P+14],j,4259657740);Y=D(Y,X,W,V,C[P+1],o,2763975236);V=D(V,Y,X,W,C[P+4],m,1272893353);W=D(W,V,Y,X,C[P+7],l,4139469664);X=D(X,W,V,Y,C[P+10],j,3200236656);Y=D(Y,X,W,V,C[P+13],o,681279174);V=D(V,Y,X,W,C[P+0],m,3936430074);W=D(W,V,Y,X,C[P+3],l,3572445317);X=D(X,W,V,Y,C[P+6],j,76029189);Y=D(Y,X,W,V,C[P+9],o,3654602809);V=D(V,Y,X,W,C[P+12],m,3873151461);W=D(W,V,Y,X,C[P+15],l,530742520);X=D(X,W,V,Y,C[P+2],j,3299628645);Y=t(Y,X,W,V,C[P+0],U,4096336452);V=t(V,Y,X,W,C[P+7],T,1126891415);W=t(W,V,Y,X,C[P+14],R,2878612391);X=t(X,W,V,Y,C[P+5],O,4237533241);Y=t(Y,X,W,V,C[P+12],U,1700485571);V=t(V,Y,X,W,C[P+3],T,2399980690);W=t(W,V,Y,X,C[P+10],R,4293915773);X=t(X,W,V,Y,C[P+1],O,2240044497);Y=t(Y,X,W,V,C[P+8],U,1873313359);V=t(V,Y,X,W,C[P+15],T,4264355552);W=t(W,V,Y,X,C[P+6],R,2734768916);X=t(X,W,V,Y,C[P+13],O,1309151649);Y=t(Y,X,W,V,C[P+4],U,4149444226);V=t(V,Y,X,W,C[P+11],T,3174756917);W=t(W,V,Y,X,C[P+2],R,718787259);X=t(X,W,V,Y,C[P+9],O,3951481745);Y=K(Y,h);X=K(X,E);W=K(W,v);V=K(V,g)}var i=B(Y)+B(X)+B(W)+B(V);return i.toLowerCase()}; var url = 'http://www.gravatar.com/avatar/' + MD5(email) + ".jpg?s=200&r=g"; console.log(url); return url; } }; });